cregit-Linux how code gets into the kernel

Release 4.14 drivers/uio/uio_fsl_elbc_gpcm.c

Directory: drivers/uio
/* uio_fsl_elbc_gpcm: UIO driver for eLBC/GPCM peripherals

   Copyright (C) 2014 Linutronix GmbH
     Author: John Ogness <john.ogness@linutronix.de>

   This driver provides UIO access to memory of a peripheral connected
   to the Freescale enhanced local bus controller (eLBC) interface
   using the general purpose chip-select mode (GPCM).

   Here is an example of the device tree entries:

        localbus@ffe05000 {
                ranges = <0x2 0x0 0x0 0xff810000 0x10000>;

                dpm@2,0 {
                        compatible = "fsl,elbc-gpcm-uio";
                        reg = <0x2 0x0 0x10000>;
                        elbc-gpcm-br = <0xff810800>;
                        elbc-gpcm-or = <0xffff09f7>;
                        interrupt-parent = <&mpic>;
                        interrupts = <4 1>;
                        device_type = "netx5152";
                        uio_name = "netx_custom";
                        netx5152,init-win0-offset = <0x0>;
                };
        };

   Only the entries reg (to identify bank) and elbc-gpcm-* (initial BR/OR
   values) are required. The entries interrupt*, device_type, and uio_name
   are optional (as well as any type-specific options such as
   netx5152,init-win0-offset). As long as no interrupt handler is needed,
   this driver can be used without any type-specific implementation.

   The netx5152 type has been tested to work with the netX 51/52 hardware
   from Hilscher using the Hilscher userspace netX stack.

   The netx5152 type should serve as a model to add new type-specific
   devices as needed.
*/

#include <linux/module.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

#include <asm/fsl_lbc.h>


#define MAX_BANKS 8


struct fsl_elbc_gpcm {
	
struct device *dev;
	
struct fsl_lbc_regs __iomem *lbc;
	
u32 bank;
	
const char *name;

	
void (*init)(struct uio_info *info);
	
void (*shutdown)(struct uio_info *info, bool init_err);
	
irqreturn_t (*irq_handler)(int irq, struct uio_info *info);
};

static ssize_t reg_show(struct device *dev, struct device_attribute *attr,
			char *buf);
static ssize_t reg_store(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);

DEVICE_ATTR(reg_br, S_IRUGO|S_IWUSR|S_IWGRP, reg_show, reg_store);
DEVICE_ATTR(reg_or, S_IRUGO|S_IWUSR|S_IWGRP, reg_show, reg_store);


static ssize_t reg_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct uio_info *info = platform_get_drvdata(pdev); struct fsl_elbc_gpcm *priv = info->priv; struct fsl_lbc_bank *bank = &priv->lbc->bank[priv->bank]; if (attr == &dev_attr_reg_br) { return scnprintf(buf, PAGE_SIZE, "0x%08x\n", in_be32(&bank->br)); } else if (attr == &dev_attr_reg_or) { return scnprintf(buf, PAGE_SIZE, "0x%08x\n", in_be32(&bank->or)); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness124100.00%1100.00%
Total124100.00%1100.00%


static ssize_t reg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *pdev = to_platform_device(dev); struct uio_info *info = platform_get_drvdata(pdev); struct fsl_elbc_gpcm *priv = info->priv; struct fsl_lbc_bank *bank = &priv->lbc->bank[priv->bank]; unsigned long val; u32 reg_br_cur; u32 reg_or_cur; u32 reg_new; /* parse use input */ if (kstrtoul(buf, 0, &val) != 0) return -EINVAL; reg_new = (u32)val; /* read current values */ reg_br_cur = in_be32(&bank->br); reg_or_cur = in_be32(&bank->or); if (attr == &dev_attr_reg_br) { /* not allowed to change effective base address */ if ((reg_br_cur & reg_or_cur & BR_BA) != (reg_new & reg_or_cur & BR_BA)) { return -EINVAL; } /* not allowed to change mode */ if ((reg_new & BR_MSEL) != BR_MS_GPCM) return -EINVAL; /* write new value (force valid) */ out_be32(&bank->br, reg_new | BR_V); } else if (attr == &dev_attr_reg_or) { /* not allowed to change access mask */ if ((reg_or_cur & OR_GPCM_AM) != (reg_new & OR_GPCM_AM)) return -EINVAL; /* write new value */ out_be32(&bank->or, reg_new); } else { return -EINVAL; } return count; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness242100.00%1100.00%
Total242100.00%1100.00%

#ifdef CONFIG_UIO_FSL_ELBC_GPCM_NETX5152 #define DPM_HOST_WIN0_OFFSET 0xff00 #define DPM_HOST_INT_STAT0 0xe0 #define DPM_HOST_INT_EN0 0xf0 #define DPM_HOST_INT_MASK 0xe600ffff #define DPM_HOST_INT_GLOBAL_EN 0x80000000
static irqreturn_t netx5152_irq_handler(int irq, struct uio_info *info) { void __iomem *reg_int_en = info->mem[0].internal_addr + DPM_HOST_WIN0_OFFSET + DPM_HOST_INT_EN0; void __iomem *reg_int_stat = info->mem[0].internal_addr + DPM_HOST_WIN0_OFFSET + DPM_HOST_INT_STAT0; /* check if an interrupt is enabled and active */ if ((ioread32(reg_int_en) & ioread32(reg_int_stat) & DPM_HOST_INT_MASK) == 0) { return IRQ_NONE; } /* disable interrupts */ iowrite32(ioread32(reg_int_en) & ~DPM_HOST_INT_GLOBAL_EN, reg_int_en); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness91100.00%1100.00%
Total91100.00%1100.00%


static void netx5152_init(struct uio_info *info) { unsigned long win0_offset = DPM_HOST_WIN0_OFFSET; struct fsl_elbc_gpcm *priv = info->priv; const void *prop; /* get an optional initial win0 offset */ prop = of_get_property(priv->dev->of_node, "netx5152,init-win0-offset", NULL); if (prop) win0_offset = of_read_ulong(prop, 1); /* disable interrupts */ iowrite32(0, info->mem[0].internal_addr + win0_offset + DPM_HOST_INT_EN0); }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness79100.00%1100.00%
Total79100.00%1100.00%


static void netx5152_shutdown(struct uio_info *info, bool init_err) { if (init_err) return; /* disable interrupts */ iowrite32(0, info->mem[0].internal_addr + DPM_HOST_WIN0_OFFSET + DPM_HOST_INT_EN0); }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness38100.00%1100.00%
Total38100.00%1100.00%

#endif
static void setup_periph(struct fsl_elbc_gpcm *priv, const char *type) { #ifdef CONFIG_UIO_FSL_ELBC_GPCM_NETX5152 if (strcmp(type, "netx5152") == 0) { priv->irq_handler = netx5152_irq_handler; priv->init = netx5152_init; priv->shutdown = netx5152_shutdown; priv->name = "netX 51/52"; return; } #endif }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness59100.00%1100.00%
Total59100.00%1100.00%


static int check_of_data(struct fsl_elbc_gpcm *priv, struct resource *res, u32 reg_br, u32 reg_or) { /* check specified bank */ if (priv->bank >= MAX_BANKS) { dev_err(priv->dev, "invalid bank\n"); return -ENODEV; } /* check specified mode (BR_MS_GPCM is 0) */ if ((reg_br & BR_MSEL) != BR_MS_GPCM) { dev_err(priv->dev, "unsupported mode\n"); return -ENODEV; } /* check specified mask vs. resource size */ if ((~(reg_or & OR_GPCM_AM) + 1) != resource_size(res)) { dev_err(priv->dev, "address mask / size mismatch\n"); return -ENODEV; } /* check specified address */ if ((reg_br & reg_or & BR_BA) != fsl_lbc_addr(res->start)) { dev_err(priv->dev, "base address mismatch\n"); return -ENODEV; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness142100.00%1100.00%
Total142100.00%1100.00%


static int get_of_data(struct fsl_elbc_gpcm *priv, struct device_node *node, struct resource *res, u32 *reg_br, u32 *reg_or, unsigned int *irq, char **name) { const char *dt_name; const char *type; int ret; /* get the memory resource */ ret = of_address_to_resource(node, 0, res); if (ret) { dev_err(priv->dev, "failed to get resource\n"); return ret; } /* get the bank number */ ret = of_property_read_u32(node, "reg", &priv->bank); if (ret) { dev_err(priv->dev, "failed to get bank number\n"); return ret; } /* get BR value to set */ ret = of_property_read_u32(node, "elbc-gpcm-br", reg_br); if (ret) { dev_err(priv->dev, "missing elbc-gpcm-br value\n"); return ret; } /* get OR value to set */ ret = of_property_read_u32(node, "elbc-gpcm-or", reg_or); if (ret) { dev_err(priv->dev, "missing elbc-gpcm-or value\n"); return ret; } /* get optional peripheral type */ priv->name = "generic"; if (of_property_read_string(node, "device_type", &type) == 0) setup_periph(priv, type); /* get optional irq value */ *irq = irq_of_parse_and_map(node, 0); /* sanity check device tree data */ ret = check_of_data(priv, res, *reg_br, *reg_or); if (ret) return ret; /* get optional uio name */ if (of_property_read_string(node, "uio_name", &dt_name) != 0) dt_name = "eLBC_GPCM"; *name = kstrdup(dt_name, GFP_KERNEL); if (!*name) return -ENOMEM; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness279100.00%1100.00%
Total279100.00%1100.00%


static int uio_fsl_elbc_gpcm_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct fsl_elbc_gpcm *priv; struct uio_info *info; char *uio_name = NULL; struct resource res; unsigned int irq; u32 reg_br_cur; u32 reg_or_cur; u32 reg_br_new; u32 reg_or_new; int ret; if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs) return -ENODEV; /* allocate private data */ priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = &pdev->dev; priv->lbc = fsl_lbc_ctrl_dev->regs; /* get device tree data */ ret = get_of_data(priv, node, &res, &reg_br_new, &reg_or_new, &irq, &uio_name); if (ret) goto out_err0; /* allocate UIO structure */ info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { ret = -ENOMEM; goto out_err0; } /* get current BR/OR values */ reg_br_cur = in_be32(&priv->lbc->bank[priv->bank].br); reg_or_cur = in_be32(&priv->lbc->bank[priv->bank].or); /* if bank already configured, make sure it matches */ if ((reg_br_cur & BR_V)) { if ((reg_br_cur & BR_MSEL) != BR_MS_GPCM || (reg_br_cur & reg_or_cur & BR_BA) != fsl_lbc_addr(res.start)) { dev_err(priv->dev, "bank in use by another peripheral\n"); ret = -ENODEV; goto out_err1; } /* warn if behavior settings changing */ if ((reg_br_cur & ~(BR_BA | BR_V)) != (reg_br_new & ~(BR_BA | BR_V))) { dev_warn(priv->dev, "modifying BR settings: 0x%08x -> 0x%08x", reg_br_cur, reg_br_new); } if ((reg_or_cur & ~OR_GPCM_AM) != (reg_or_new & ~OR_GPCM_AM)) { dev_warn(priv->dev, "modifying OR settings: 0x%08x -> 0x%08x", reg_or_cur, reg_or_new); } } /* configure the bank (force base address and GPCM) */ reg_br_new &= ~(BR_BA | BR_MSEL); reg_br_new |= fsl_lbc_addr(res.start) | BR_MS_GPCM | BR_V; out_be32(&priv->lbc->bank[priv->bank].or, reg_or_new); out_be32(&priv->lbc->bank[priv->bank].br, reg_br_new); /* map the memory resource */ info->mem[0].internal_addr = ioremap(res.start, resource_size(&res)); if (!info->mem[0].internal_addr) { dev_err(priv->dev, "failed to map chip region\n"); ret = -ENODEV; goto out_err1; } /* set all UIO data */ if (node->name) info->mem[0].name = kstrdup(node->name, GFP_KERNEL); info->mem[0].addr = res.start; info->mem[0].size = resource_size(&res); info->mem[0].memtype = UIO_MEM_PHYS; info->priv = priv; info->name = uio_name; info->version = "0.0.1"; if (irq != NO_IRQ) { if (priv->irq_handler) { info->irq = irq; info->irq_flags = IRQF_SHARED; info->handler = priv->irq_handler; } else { irq = NO_IRQ; dev_warn(priv->dev, "ignoring irq, no handler\n"); } } if (priv->init) priv->init(info); /* register UIO device */ if (uio_register_device(priv->dev, info) != 0) { dev_err(priv->dev, "UIO registration failed\n"); ret = -ENODEV; goto out_err2; } /* store private data */ platform_set_drvdata(pdev, info); /* create sysfs files */ ret = device_create_file(priv->dev, &dev_attr_reg_br); if (ret) goto out_err3; ret = device_create_file(priv->dev, &dev_attr_reg_or); if (ret) goto out_err4; dev_info(priv->dev, "eLBC/GPCM device (%s) at 0x%llx, bank %d, irq=%d\n", priv->name, (unsigned long long)res.start, priv->bank, irq != NO_IRQ ? irq : -1); return 0; out_err4: device_remove_file(priv->dev, &dev_attr_reg_br); out_err3: platform_set_drvdata(pdev, NULL); uio_unregister_device(info); out_err2: if (priv->shutdown) priv->shutdown(info, true); iounmap(info->mem[0].internal_addr); out_err1: kfree(info->mem[0].name); kfree(info); out_err0: kfree(uio_name); kfree(priv); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness810100.00%1100.00%
Total810100.00%1100.00%


static int uio_fsl_elbc_gpcm_remove(struct platform_device *pdev) { struct uio_info *info = platform_get_drvdata(pdev); struct fsl_elbc_gpcm *priv = info->priv; device_remove_file(priv->dev, &dev_attr_reg_or); device_remove_file(priv->dev, &dev_attr_reg_br); platform_set_drvdata(pdev, NULL); uio_unregister_device(info); if (priv->shutdown) priv->shutdown(info, false); iounmap(info->mem[0].internal_addr); kfree(info->mem[0].name); kfree(info->name); kfree(info); kfree(priv); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
John Ogness121100.00%1100.00%
Total121100.00%1100.00%

static const struct of_device_id uio_fsl_elbc_gpcm_match[] = { { .compatible = "fsl,elbc-gpcm-uio", }, {} }; MODULE_DEVICE_TABLE(of, uio_fsl_elbc_gpcm_match); static struct platform_driver uio_fsl_elbc_gpcm_driver = { .driver = { .name = "fsl,elbc-gpcm-uio", .owner = THIS_MODULE, .of_match_table = uio_fsl_elbc_gpcm_match, }, .probe = uio_fsl_elbc_gpcm_probe, .remove = uio_fsl_elbc_gpcm_remove, }; module_platform_driver(uio_fsl_elbc_gpcm_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); MODULE_DESCRIPTION("Freescale Enhanced Local Bus Controller GPCM driver");

Overall Contributors

PersonTokensPropCommitsCommitProp
John Ogness225399.60%133.33%
Luis de Bethencourt70.31%133.33%
Vaishali Thakkar20.09%133.33%
Total2262100.00%3100.00%
Directory: drivers/uio
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.