Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Théo Lebrun | 2966 | 87.78% | 11 | 23.91% |
Linus Walleij | 152 | 4.50% | 12 | 26.09% |
Rabin Vincent | 105 | 3.11% | 9 | 19.57% |
Alessandro Rubini | 74 | 2.19% | 1 | 2.17% |
Lee Jones | 29 | 0.86% | 3 | 6.52% |
Andy Shevchenko | 25 | 0.74% | 2 | 4.35% |
Masahiro Yamada | 8 | 0.24% | 1 | 2.17% |
Dan Carpenter | 6 | 0.18% | 1 | 2.17% |
Patrice Chotard | 5 | 0.15% | 1 | 2.17% |
Bartosz Golaszewski | 3 | 0.09% | 1 | 2.17% |
Ulf Hansson | 2 | 0.06% | 1 | 2.17% |
Rob Herring | 2 | 0.06% | 1 | 2.17% |
Thomas Gleixner | 1 | 0.03% | 1 | 2.17% |
Sherman Yin | 1 | 0.03% | 1 | 2.17% |
Total | 3379 | 46 |
// SPDX-License-Identifier: GPL-2.0-only /* * GPIO driver for the IP block found in the Nomadik SoC; it is an AMBA device, * managing 32 pins with alternate functions. It can also handle the STA2X11 * block from ST. * * The GPIO chips are shared with pinctrl-nomadik if used; it needs access for * pinmuxing functionality and others. * * This driver also handles the mobileye,eyeq5-gpio compatible. It is an STA2X11 * but with only data, direction and interrupts register active. We want to * avoid touching SLPM, RWIMSC, FWIMSC, AFSLA and AFSLB registers; that is, * wake and alternate function registers. It is NOT compatible with * pinctrl-nomadik. * * Copyright (C) 2008,2009 STMicroelectronics * Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it> * Rewritten based on work by Prafulla WADASKAR <prafulla.wadaskar@st.com> * Copyright (C) 2011-2013 Linus Walleij <linus.walleij@linaro.org> */ #include <linux/cleanup.h> #include <linux/clk.h> #include <linux/gpio/driver.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/mod_devicetable.h> #include <linux/pinctrl/pinctrl.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/reset.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/gpio/gpio-nomadik.h> #ifndef CONFIG_PINCTRL_NOMADIK static DEFINE_SPINLOCK(nmk_gpio_slpm_lock); #endif void __nmk_gpio_set_slpm(struct nmk_gpio_chip *nmk_chip, unsigned int offset, enum nmk_gpio_slpm mode) { u32 slpm; /* We should NOT have been called. */ if (WARN_ON(nmk_chip->is_mobileye_soc)) return; slpm = readl(nmk_chip->addr + NMK_GPIO_SLPC); if (mode == NMK_GPIO_SLPM_NOCHANGE) slpm |= BIT(offset); else slpm &= ~BIT(offset); writel(slpm, nmk_chip->addr + NMK_GPIO_SLPC); } static void __nmk_gpio_set_output(struct nmk_gpio_chip *nmk_chip, unsigned int offset, int val) { if (val) writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DATS); else writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DATC); } void __nmk_gpio_make_output(struct nmk_gpio_chip *nmk_chip, unsigned int offset, int val) { writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DIRS); __nmk_gpio_set_output(nmk_chip, offset, val); } /* IRQ functions */ static void nmk_gpio_irq_ack(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); clk_enable(nmk_chip->clk); writel(BIT(d->hwirq), nmk_chip->addr + NMK_GPIO_IC); clk_disable(nmk_chip->clk); } enum nmk_gpio_irq_type { NORMAL, WAKE, }; static void __nmk_gpio_irq_modify(struct nmk_gpio_chip *nmk_chip, int offset, enum nmk_gpio_irq_type which, bool enable) { u32 *rimscval; u32 *fimscval; u32 rimscreg; u32 fimscreg; if (which == NORMAL) { rimscreg = NMK_GPIO_RIMSC; fimscreg = NMK_GPIO_FIMSC; rimscval = &nmk_chip->rimsc; fimscval = &nmk_chip->fimsc; } else { /* We should NOT have been called. */ if (WARN_ON(nmk_chip->is_mobileye_soc)) return; rimscreg = NMK_GPIO_RWIMSC; fimscreg = NMK_GPIO_FWIMSC; rimscval = &nmk_chip->rwimsc; fimscval = &nmk_chip->fwimsc; } /* we must individually set/clear the two edges */ if (nmk_chip->edge_rising & BIT(offset)) { if (enable) *rimscval |= BIT(offset); else *rimscval &= ~BIT(offset); writel(*rimscval, nmk_chip->addr + rimscreg); } if (nmk_chip->edge_falling & BIT(offset)) { if (enable) *fimscval |= BIT(offset); else *fimscval &= ~BIT(offset); writel(*fimscval, nmk_chip->addr + fimscreg); } } static void __nmk_gpio_set_wake(struct nmk_gpio_chip *nmk_chip, int offset, bool on) { /* We should NOT have been called. */ if (WARN_ON(nmk_chip->is_mobileye_soc)) return; /* * Ensure WAKEUP_ENABLE is on. No need to disable it if wakeup is * disabled, since setting SLPM to 1 increases power consumption, and * wakeup is anyhow controlled by the RIMSC and FIMSC registers. */ if (nmk_chip->sleepmode && on) { __nmk_gpio_set_slpm(nmk_chip, offset, NMK_GPIO_SLPM_WAKEUP_ENABLE); } __nmk_gpio_irq_modify(nmk_chip, offset, WAKE, on); } static void nmk_gpio_irq_maskunmask(struct nmk_gpio_chip *nmk_chip, struct irq_data *d, bool enable) { unsigned long flags; clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); spin_lock(&nmk_chip->lock); __nmk_gpio_irq_modify(nmk_chip, d->hwirq, NORMAL, enable); if (!nmk_chip->is_mobileye_soc && !(nmk_chip->real_wake & BIT(d->hwirq))) __nmk_gpio_set_wake(nmk_chip, d->hwirq, enable); spin_unlock(&nmk_chip->lock); spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); clk_disable(nmk_chip->clk); } static void nmk_gpio_irq_mask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); nmk_gpio_irq_maskunmask(nmk_chip, d, false); gpiochip_disable_irq(gc, irqd_to_hwirq(d)); } static void nmk_gpio_irq_unmask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); gpiochip_enable_irq(gc, irqd_to_hwirq(d)); nmk_gpio_irq_maskunmask(nmk_chip, d, true); } static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); unsigned long flags; /* Handler is registered in all cases. */ if (nmk_chip->is_mobileye_soc) return -ENXIO; clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); spin_lock(&nmk_chip->lock); if (irqd_irq_disabled(d)) __nmk_gpio_set_wake(nmk_chip, d->hwirq, on); if (on) nmk_chip->real_wake |= BIT(d->hwirq); else nmk_chip->real_wake &= ~BIT(d->hwirq); spin_unlock(&nmk_chip->lock); spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); clk_disable(nmk_chip->clk); return 0; } static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); bool enabled = !irqd_irq_disabled(d); bool wake = irqd_is_wakeup_set(d); unsigned long flags; if (type & IRQ_TYPE_LEVEL_HIGH) return -EINVAL; if (type & IRQ_TYPE_LEVEL_LOW) return -EINVAL; clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_chip->lock, flags); if (enabled) __nmk_gpio_irq_modify(nmk_chip, d->hwirq, NORMAL, false); if (!nmk_chip->is_mobileye_soc && (enabled || wake)) __nmk_gpio_irq_modify(nmk_chip, d->hwirq, WAKE, false); nmk_chip->edge_rising &= ~BIT(d->hwirq); if (type & IRQ_TYPE_EDGE_RISING) nmk_chip->edge_rising |= BIT(d->hwirq); nmk_chip->edge_falling &= ~BIT(d->hwirq); if (type & IRQ_TYPE_EDGE_FALLING) nmk_chip->edge_falling |= BIT(d->hwirq); if (enabled) __nmk_gpio_irq_modify(nmk_chip, d->hwirq, NORMAL, true); if (!nmk_chip->is_mobileye_soc && (enabled || wake)) __nmk_gpio_irq_modify(nmk_chip, d->hwirq, WAKE, true); spin_unlock_irqrestore(&nmk_chip->lock, flags); clk_disable(nmk_chip->clk); return 0; } static unsigned int nmk_gpio_irq_startup(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); clk_enable(nmk_chip->clk); nmk_gpio_irq_unmask(d); return 0; } static void nmk_gpio_irq_shutdown(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); nmk_gpio_irq_mask(d); clk_disable(nmk_chip->clk); } static irqreturn_t nmk_gpio_irq_handler(int irq, void *dev_id) { struct nmk_gpio_chip *nmk_chip = dev_id; struct gpio_chip *chip = &nmk_chip->chip; unsigned long mask = GENMASK(chip->ngpio - 1, 0); unsigned long status; int bit; clk_enable(nmk_chip->clk); status = readl(nmk_chip->addr + NMK_GPIO_IS); /* Ensure we cannot leave pending bits; this should never occur. */ if (unlikely(status & ~mask)) writel(status & ~mask, nmk_chip->addr + NMK_GPIO_IC); clk_disable(nmk_chip->clk); for_each_set_bit(bit, &status, chip->ngpio) generic_handle_domain_irq_safe(chip->irq.domain, bit); return IRQ_RETVAL((status & mask) != 0); } /* I/O Functions */ static int nmk_gpio_get_dir(struct gpio_chip *chip, unsigned int offset) { struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); int dir; clk_enable(nmk_chip->clk); dir = readl(nmk_chip->addr + NMK_GPIO_DIR) & BIT(offset); clk_disable(nmk_chip->clk); if (dir) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; } static int nmk_gpio_make_input(struct gpio_chip *chip, unsigned int offset) { struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); clk_enable(nmk_chip->clk); writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DIRC); clk_disable(nmk_chip->clk); return 0; } static int nmk_gpio_get_input(struct gpio_chip *chip, unsigned int offset) { struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); int value; clk_enable(nmk_chip->clk); value = !!(readl(nmk_chip->addr + NMK_GPIO_DAT) & BIT(offset)); clk_disable(nmk_chip->clk); return value; } static void nmk_gpio_set_output(struct gpio_chip *chip, unsigned int offset, int val) { struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); clk_enable(nmk_chip->clk); __nmk_gpio_set_output(nmk_chip, offset, val); clk_disable(nmk_chip->clk); } static int nmk_gpio_make_output(struct gpio_chip *chip, unsigned int offset, int val) { struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); clk_enable(nmk_chip->clk); __nmk_gpio_make_output(nmk_chip, offset, val); clk_disable(nmk_chip->clk); return 0; } #ifdef CONFIG_DEBUG_FS static int nmk_gpio_get_mode(struct nmk_gpio_chip *nmk_chip, int offset) { u32 afunc, bfunc; /* We don't support modes. */ if (nmk_chip->is_mobileye_soc) return NMK_GPIO_ALT_GPIO; clk_enable(nmk_chip->clk); afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & BIT(offset); bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & BIT(offset); clk_disable(nmk_chip->clk); return (afunc ? NMK_GPIO_ALT_A : 0) | (bfunc ? NMK_GPIO_ALT_B : 0); } void nmk_gpio_dbg_show_one(struct seq_file *s, struct pinctrl_dev *pctldev, struct gpio_chip *chip, unsigned int offset, unsigned int gpio) { struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); int mode; bool is_out; bool data_out; bool pull; static const char * const modes[] = { [NMK_GPIO_ALT_GPIO] = "gpio", [NMK_GPIO_ALT_A] = "altA", [NMK_GPIO_ALT_B] = "altB", [NMK_GPIO_ALT_C] = "altC", [NMK_GPIO_ALT_C + 1] = "altC1", [NMK_GPIO_ALT_C + 2] = "altC2", [NMK_GPIO_ALT_C + 3] = "altC3", [NMK_GPIO_ALT_C + 4] = "altC4", }; char *label = gpiochip_dup_line_label(chip, offset); if (IS_ERR(label)) return; clk_enable(nmk_chip->clk); is_out = !!(readl(nmk_chip->addr + NMK_GPIO_DIR) & BIT(offset)); pull = !(readl(nmk_chip->addr + NMK_GPIO_PDIS) & BIT(offset)); data_out = !!(readl(nmk_chip->addr + NMK_GPIO_DAT) & BIT(offset)); mode = nmk_gpio_get_mode(nmk_chip, offset); #ifdef CONFIG_PINCTRL_NOMADIK if (mode == NMK_GPIO_ALT_C && pctldev) mode = nmk_prcm_gpiocr_get_mode(pctldev, gpio); #endif if (is_out) { seq_printf(s, " gpio-%-3d (%-20.20s) out %s %s", gpio, label ?: "(none)", data_out ? "hi" : "lo", (mode < 0) ? "unknown" : modes[mode]); } else { int irq = chip->to_irq(chip, offset); const int pullidx = pull ? 1 : 0; int val; static const char * const pulls[] = { "none ", "pull enabled", }; seq_printf(s, " gpio-%-3d (%-20.20s) in %s %s", gpio, label ?: "(none)", pulls[pullidx], (mode < 0) ? "unknown" : modes[mode]); val = nmk_gpio_get_input(chip, offset); seq_printf(s, " VAL %d", val); /* * This races with request_irq(), set_irq_type(), * and set_irq_wake() ... but those are "rare". */ if (irq > 0 && irq_has_action(irq)) { char *trigger; bool wake; if (nmk_chip->edge_rising & BIT(offset)) trigger = "edge-rising"; else if (nmk_chip->edge_falling & BIT(offset)) trigger = "edge-falling"; else trigger = "edge-undefined"; wake = !!(nmk_chip->real_wake & BIT(offset)); seq_printf(s, " irq-%d %s%s", irq, trigger, wake ? " wakeup" : ""); } } clk_disable(nmk_chip->clk); } static void nmk_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) { unsigned int i, gpio = chip->base; for (i = 0; i < chip->ngpio; i++, gpio++) { nmk_gpio_dbg_show_one(s, NULL, chip, i, gpio); seq_puts(s, "\n"); } } #else #define nmk_gpio_dbg_show NULL #endif /* * We will allocate memory for the state container using devm* allocators * binding to the first device reaching this point, it doesn't matter if * it is the pin controller or GPIO driver. However we need to use the right * platform device when looking up resources so pay attention to pdev. */ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, struct platform_device *pdev) { struct nmk_gpio_chip *nmk_chip; struct platform_device *gpio_pdev; struct device *dev = &pdev->dev; struct reset_control *reset; struct device *gpio_dev; struct gpio_chip *chip; struct resource *res; struct clk *clk; void __iomem *base; u32 id, ngpio; int ret; gpio_dev = bus_find_device_by_fwnode(&platform_bus_type, fwnode); if (!gpio_dev) { dev_err(dev, "populate \"%pfwP\": device not found\n", fwnode); return ERR_PTR(-ENODEV); } gpio_pdev = to_platform_device(gpio_dev); if (device_property_read_u32(gpio_dev, "gpio-bank", &id)) { dev_err(dev, "populate: gpio-bank property not found\n"); platform_device_put(gpio_pdev); return ERR_PTR(-EINVAL); } #ifdef CONFIG_PINCTRL_NOMADIK if (id >= ARRAY_SIZE(nmk_gpio_chips)) { dev_err(dev, "populate: invalid id: %u\n", id); platform_device_put(gpio_pdev); return ERR_PTR(-EINVAL); } /* Already populated? */ nmk_chip = nmk_gpio_chips[id]; if (nmk_chip) { platform_device_put(gpio_pdev); return nmk_chip; } #endif nmk_chip = devm_kzalloc(dev, sizeof(*nmk_chip), GFP_KERNEL); if (!nmk_chip) { platform_device_put(gpio_pdev); return ERR_PTR(-ENOMEM); } if (device_property_read_u32(gpio_dev, "ngpios", &ngpio)) { ngpio = NMK_GPIO_PER_CHIP; dev_dbg(dev, "populate: using default ngpio (%u)\n", ngpio); } nmk_chip->is_mobileye_soc = device_is_compatible(gpio_dev, "mobileye,eyeq5-gpio"); nmk_chip->bank = id; chip = &nmk_chip->chip; chip->base = -1; chip->ngpio = ngpio; chip->label = dev_name(gpio_dev); chip->parent = gpio_dev; /* NOTE: different devices! No devm_platform_ioremap_resource() here! */ res = platform_get_resource(gpio_pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) { platform_device_put(gpio_pdev); return ERR_CAST(base); } nmk_chip->addr = base; /* NOTE: do not use devm_ here! */ clk = clk_get_optional(gpio_dev, NULL); if (IS_ERR(clk)) { platform_device_put(gpio_pdev); return ERR_CAST(clk); } clk_prepare(clk); nmk_chip->clk = clk; /* NOTE: do not use devm_ here! */ reset = reset_control_get_optional_shared(gpio_dev, NULL); if (IS_ERR(reset)) { clk_unprepare(clk); clk_put(clk); platform_device_put(gpio_pdev); dev_err(dev, "failed getting reset control: %pe\n", reset); return ERR_CAST(reset); } /* * Reset might be shared and asserts/deasserts calls are unbalanced. We * only support sharing this reset with other gpio-nomadik devices that * use this reset to ensure deassertion at probe. */ ret = reset_control_deassert(reset); if (ret) { reset_control_put(reset); clk_unprepare(clk); clk_put(clk); platform_device_put(gpio_pdev); dev_err(dev, "failed reset deassert: %d\n", ret); return ERR_PTR(ret); } #ifdef CONFIG_PINCTRL_NOMADIK nmk_gpio_chips[id] = nmk_chip; #endif return nmk_chip; } static void nmk_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); seq_printf(p, "nmk%u-%u-%u", nmk_chip->bank, gc->base, gc->base + gc->ngpio - 1); } static const struct irq_chip nmk_irq_chip = { .irq_ack = nmk_gpio_irq_ack, .irq_mask = nmk_gpio_irq_mask, .irq_unmask = nmk_gpio_irq_unmask, .irq_set_type = nmk_gpio_irq_set_type, .irq_set_wake = nmk_gpio_irq_set_wake, .irq_startup = nmk_gpio_irq_startup, .irq_shutdown = nmk_gpio_irq_shutdown, .irq_print_chip = nmk_gpio_irq_print_chip, .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE, GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int nmk_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct nmk_gpio_chip *nmk_chip; struct gpio_irq_chip *girq; bool supports_sleepmode; struct gpio_chip *chip; int irq; int ret; nmk_chip = nmk_gpio_populate_chip(dev_fwnode(dev), pdev); if (IS_ERR(nmk_chip)) { dev_err(dev, "could not populate nmk chip struct\n"); return PTR_ERR(nmk_chip); } supports_sleepmode = device_property_read_bool(dev, "st,supports-sleepmode"); /* Correct platform device ID */ pdev->id = nmk_chip->bank; irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; /* * The virt address in nmk_chip->addr is in the nomadik register space, * so we can simply convert the resource address, without remapping */ nmk_chip->sleepmode = supports_sleepmode; spin_lock_init(&nmk_chip->lock); chip = &nmk_chip->chip; chip->parent = dev; chip->request = gpiochip_generic_request; chip->free = gpiochip_generic_free; chip->get_direction = nmk_gpio_get_dir; chip->direction_input = nmk_gpio_make_input; chip->get = nmk_gpio_get_input; chip->direction_output = nmk_gpio_make_output; chip->set = nmk_gpio_set_output; chip->dbg_show = nmk_gpio_dbg_show; chip->can_sleep = false; chip->owner = THIS_MODULE; girq = &chip->irq; gpio_irq_chip_set_chip(girq, &nmk_irq_chip); girq->parent_handler = NULL; girq->num_parents = 0; girq->parents = NULL; girq->default_type = IRQ_TYPE_NONE; girq->handler = handle_edge_irq; ret = devm_request_irq(dev, irq, nmk_gpio_irq_handler, IRQF_SHARED, dev_name(dev), nmk_chip); if (ret) { dev_err(dev, "failed requesting IRQ\n"); return ret; } if (!nmk_chip->is_mobileye_soc) { clk_enable(nmk_chip->clk); nmk_chip->lowemi = readl_relaxed(nmk_chip->addr + NMK_GPIO_LOWEMI); clk_disable(nmk_chip->clk); } ret = gpiochip_add_data(chip, nmk_chip); if (ret) return ret; platform_set_drvdata(pdev, nmk_chip); dev_info(dev, "chip registered\n"); return 0; } static const struct of_device_id nmk_gpio_match[] = { { .compatible = "st,nomadik-gpio", }, { .compatible = "mobileye,eyeq5-gpio", }, {} }; static struct platform_driver nmk_gpio_driver = { .driver = { .name = "nomadik-gpio", .of_match_table = nmk_gpio_match, .suppress_bind_attrs = true, }, .probe = nmk_gpio_probe, }; static int __init nmk_gpio_init(void) { return platform_driver_register(&nmk_gpio_driver); } subsys_initcall(nmk_gpio_init);
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1