diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-04-06 02:37:40 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-04-06 02:37:40 +0400 |
commit | cbda94e039c3862326a65d1d0506447af8330c3c (patch) | |
tree | 1147da54ec6eb7e1081977f07e62d514b981d9a3 /drivers/reset/sti/reset-syscfg.c | |
parent | f83ccb93585d1f472c30fa2bbb8b56c23dbdb506 (diff) | |
parent | f1d7d8c86bc8ca41c88acf10ce383c5104cf4920 (diff) | |
download | linux-cbda94e039c3862326a65d1d0506447af8330c3c.tar.xz |
Merge tag 'drivers-3.15' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC driver changes from Arnd Bergmann:
"These changes are mostly for ARM specific device drivers that either
don't have an upstream maintainer, or that had the maintainer ask us
to pick up the changes to avoid conflicts.
A large chunk of this are clock drivers (bcm281xx, exynos, versatile,
shmobile), aside from that, reset controllers for STi as well as a
large rework of the Marvell Orion/EBU watchdog driver are notable"
* tag 'drivers-3.15' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (99 commits)
Revert "dts: socfpga: Add DTS entry for adding the stmmac glue layer for stmmac."
Revert "net: stmmac: Add SOCFPGA glue driver"
ARM: shmobile: r8a7791: Fix SCIFA3-5 clocks
ARM: STi: Add reset controller support to mach-sti Kconfig
drivers: reset: stih416: add softreset controller
drivers: reset: stih415: add softreset controller
drivers: reset: Reset controller driver for STiH416
drivers: reset: Reset controller driver for STiH415
drivers: reset: STi SoC system configuration reset controller support
dts: socfpga: Add sysmgr node so the gmac can use to reference
dts: socfpga: Add support for SD/MMC on the SOCFPGA platform
reset: Add optional resets and stubs
ARM: shmobile: r7s72100: fix bus clock calculation
Power: Reset: Generalize qnap-poweroff to work on Synology devices.
dts: socfpga: Update clock entry to support multiple parents
ARM: socfpga: Update socfpga_defconfig
dts: socfpga: Add DTS entry for adding the stmmac glue layer for stmmac.
net: stmmac: Add SOCFPGA glue driver
watchdog: orion_wdt: Use %pa to print 'phys_addr_t'
drivers: cci: Export CCI PMU revision
...
Diffstat (limited to 'drivers/reset/sti/reset-syscfg.c')
-rw-r--r-- | drivers/reset/sti/reset-syscfg.c | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/drivers/reset/sti/reset-syscfg.c b/drivers/reset/sti/reset-syscfg.c new file mode 100644 index 000000000000..a145cc066d4a --- /dev/null +++ b/drivers/reset/sti/reset-syscfg.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2013 STMicroelectronics Limited + * Author: Stephen Gallimore <stephen.gallimore@st.com> + * + * Inspired by mach-imx/src.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#include "reset-syscfg.h" + +/** + * Reset channel regmap configuration + * + * @reset: regmap field for the channel's reset bit. + * @ack: regmap field for the channel's ack bit (optional). + */ +struct syscfg_reset_channel { + struct regmap_field *reset; + struct regmap_field *ack; +}; + +/** + * A reset controller which groups together a set of related reset bits, which + * may be located in different system configuration registers. + * + * @rst: base reset controller structure. + * @active_low: are the resets in this controller active low, i.e. clearing + * the reset bit puts the hardware into reset. + * @channels: An array of reset channels for this controller. + */ +struct syscfg_reset_controller { + struct reset_controller_dev rst; + bool active_low; + struct syscfg_reset_channel *channels; +}; + +#define to_syscfg_reset_controller(_rst) \ + container_of(_rst, struct syscfg_reset_controller, rst) + +static int syscfg_reset_program_hw(struct reset_controller_dev *rcdev, + unsigned long idx, int assert) +{ + struct syscfg_reset_controller *rst = to_syscfg_reset_controller(rcdev); + const struct syscfg_reset_channel *ch; + u32 ctrl_val = rst->active_low ? !assert : !!assert; + int err; + + if (idx >= rcdev->nr_resets) + return -EINVAL; + + ch = &rst->channels[idx]; + + err = regmap_field_write(ch->reset, ctrl_val); + if (err) + return err; + + if (ch->ack) { + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + u32 ack_val; + + while (true) { + err = regmap_field_read(ch->ack, &ack_val); + if (err) + return err; + + if (ack_val == ctrl_val) + break; + + if (time_after(jiffies, timeout)) + return -ETIME; + + cpu_relax(); + } + } + + return 0; +} + +static int syscfg_reset_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return syscfg_reset_program_hw(rcdev, idx, true); +} + +static int syscfg_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return syscfg_reset_program_hw(rcdev, idx, false); +} + +static int syscfg_reset_dev(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + int err = syscfg_reset_assert(rcdev, idx); + if (err) + return err; + + return syscfg_reset_deassert(rcdev, idx); +} + +static struct reset_control_ops syscfg_reset_ops = { + .reset = syscfg_reset_dev, + .assert = syscfg_reset_assert, + .deassert = syscfg_reset_deassert, +}; + +static int syscfg_reset_controller_register(struct device *dev, + const struct syscfg_reset_controller_data *data) +{ + struct syscfg_reset_controller *rc; + size_t size; + int i, err; + + rc = devm_kzalloc(dev, sizeof(*rc), GFP_KERNEL); + if (!rc) + return -ENOMEM; + + size = sizeof(struct syscfg_reset_channel) * data->nr_channels; + + rc->channels = devm_kzalloc(dev, size, GFP_KERNEL); + if (!rc->channels) + return -ENOMEM; + + rc->rst.ops = &syscfg_reset_ops, + rc->rst.of_node = dev->of_node; + rc->rst.nr_resets = data->nr_channels; + rc->active_low = data->active_low; + + for (i = 0; i < data->nr_channels; i++) { + struct regmap *map; + struct regmap_field *f; + const char *compatible = data->channels[i].compatible; + + map = syscon_regmap_lookup_by_compatible(compatible); + if (IS_ERR(map)) + return PTR_ERR(map); + + f = devm_regmap_field_alloc(dev, map, data->channels[i].reset); + if (IS_ERR(f)) + return PTR_ERR(f); + + rc->channels[i].reset = f; + + if (!data->wait_for_ack) + continue; + + f = devm_regmap_field_alloc(dev, map, data->channels[i].ack); + if (IS_ERR(f)) + return PTR_ERR(f); + + rc->channels[i].ack = f; + } + + err = reset_controller_register(&rc->rst); + if (!err) + dev_info(dev, "registered\n"); + + return err; +} + +int syscfg_reset_probe(struct platform_device *pdev) +{ + struct device *dev = pdev ? &pdev->dev : NULL; + const struct of_device_id *match; + + if (!dev || !dev->driver) + return -ENODEV; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return -EINVAL; + + return syscfg_reset_controller_register(dev, match->data); +} |