summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile2
-rw-r--r--drivers/ata/disk_ata_drive.c3
-rw-r--r--drivers/ata/ide-sff.c54
-rw-r--r--drivers/base/driver.c2
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/at91/Makefile13
-rw-r--r--drivers/clk/at91/clk-generated.c323
-rw-r--r--drivers/clk/at91/clk-h32mx.c125
-rw-r--r--drivers/clk/at91/clk-main.c576
-rw-r--r--drivers/clk/at91/clk-master.c245
-rw-r--r--drivers/clk/at91/clk-peripheral.c429
-rw-r--r--drivers/clk/at91/clk-pll.c516
-rw-r--r--drivers/clk/at91/clk-plldiv.c135
-rw-r--r--drivers/clk/at91/clk-programmable.c254
-rw-r--r--drivers/clk/at91/clk-slow.c108
-rw-r--r--drivers/clk/at91/clk-smd.c172
-rw-r--r--drivers/clk/at91/clk-system.c160
-rw-r--r--drivers/clk/at91/clk-usb.c397
-rw-r--r--drivers/clk/at91/clk-utmi.c138
-rw-r--r--drivers/clk/at91/pmc.c41
-rw-r--r--drivers/clk/at91/pmc.h27
-rw-r--r--drivers/clk/at91/sckc.c485
-rw-r--r--drivers/clk/clk-fixed-factor.c2
-rw-r--r--drivers/clk/clk-fixed.c2
-rw-r--r--drivers/clk/clk-gate-shared.c6
-rw-r--r--drivers/clocksource/mvebu.c2
-rw-r--r--drivers/clocksource/timer-atmel-pit.c9
-rw-r--r--drivers/crypto/caam/Kconfig1
-rw-r--r--drivers/crypto/caam/caamrng.c44
-rw-r--r--drivers/eeprom/at24.c1
-rw-r--r--drivers/eeprom/at25.c2
-rw-r--r--drivers/efi/efi-device.c13
-rw-r--r--drivers/hab/Makefile1
-rw-r--r--drivers/hab/hab.c358
-rw-r--r--drivers/hw_random/Kconfig6
-rw-r--r--drivers/hw_random/Makefile1
-rw-r--r--drivers/hw_random/core.c125
-rw-r--r--drivers/led/Kconfig1
-rw-r--r--drivers/led/core.c101
-rw-r--r--drivers/led/led-triggers.c157
-rw-r--r--drivers/mci/atmel_mci.c101
-rw-r--r--drivers/mci/mci-core.c17
-rw-r--r--drivers/misc/state.c6
-rw-r--r--drivers/mtd/core.c65
-rw-r--r--drivers/mtd/partition.c2
-rw-r--r--drivers/mtd/peb.c4
-rw-r--r--drivers/net/macb.c56
-rw-r--r--drivers/net/phy/marvell.c126
-rw-r--r--drivers/net/phy/mdio-gpio.c2
-rw-r--r--drivers/nvmem/Kconfig18
-rw-r--r--drivers/nvmem/Makefile10
-rw-r--r--drivers/nvmem/core.c764
-rw-r--r--drivers/nvmem/snvs_lpgpr.c115
-rw-r--r--drivers/of/base.c24
-rw-r--r--drivers/of/of_path.c107
-rw-r--r--drivers/of/partition.c176
-rw-r--r--drivers/serial/atmel.c7
-rw-r--r--drivers/spi/atmel_spi.c29
-rw-r--r--drivers/usb/gadget/Kconfig11
-rw-r--r--drivers/usb/gadget/Makefile1
-rw-r--r--drivers/usb/gadget/autostart.c70
-rw-r--r--drivers/usb/host/Kconfig5
-rw-r--r--drivers/usb/host/ehci-atmel.c11
-rw-r--r--drivers/usb/host/ohci-at91.c93
-rw-r--r--drivers/video/edid.c2
-rw-r--r--drivers/video/imx-ipu-v3/ipufb.c6
-rw-r--r--drivers/video/simplefb.c12
-rw-r--r--drivers/w1/masters/w1-gpio.c53
-rw-r--r--drivers/watchdog/Kconfig6
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/orion_wdt.c123
72 files changed, 6806 insertions, 257 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index ab9afb54bd..1e0246da6d 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -22,10 +22,12 @@ source "drivers/eeprom/Kconfig"
source "drivers/input/Kconfig"
source "drivers/watchdog/Kconfig"
source "drivers/pwm/Kconfig"
+source "drivers/hw_random/Kconfig"
source "drivers/dma/Kconfig"
source "drivers/gpio/Kconfig"
source "drivers/w1/Kconfig"
source "drivers/pinctrl/Kconfig"
+source "drivers/nvmem/Kconfig"
source "drivers/bus/Kconfig"
source "drivers/regulator/Kconfig"
source "drivers/reset/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index a1d2d23fef..767789d541 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -19,9 +19,11 @@ obj-y += eeprom/
obj-$(CONFIG_PWM) += pwm/
obj-y += input/
obj-y += misc/
+obj-$(CONFIG_NVMEM) += nvmem/
obj-y += dma/
obj-y += watchdog/
obj-y += gpio/
+obj-$(CONFIG_HWRNG) += hw_random/
obj-$(CONFIG_OFTREE) += of/
obj-$(CONFIG_W1) += w1/
obj-y += pinctrl/
diff --git a/drivers/ata/disk_ata_drive.c b/drivers/ata/disk_ata_drive.c
index 1aa1bb1456..5ebddbdec8 100644
--- a/drivers/ata/disk_ata_drive.c
+++ b/drivers/ata/disk_ata_drive.c
@@ -237,6 +237,9 @@ static int ata_port_init(struct ata_port *port)
#ifdef DEBUG
ata_dump_id(port->id);
#endif
+
+ port->lba48 = ata_id_has_lba48(port->id);
+
if (port->devname) {
port->blk.cdev.name = xstrdup(port->devname);
} else {
diff --git a/drivers/ata/ide-sff.c b/drivers/ata/ide-sff.c
index 6dc89d79a5..b7c8847266 100644
--- a/drivers/ata/ide-sff.c
+++ b/drivers/ata/ide-sff.c
@@ -136,17 +136,33 @@ static int ata_wait_ready(struct ide_port *ide, unsigned timeout)
* @param io Register file
* @param drive 0 master drive, 1 slave drive
* @param num Sector number
- *
- * @todo LBA48 support
*/
-static int ata_set_lba_sector(struct ide_port *ide, unsigned drive, uint64_t num)
+static int ata_set_lba_sector(struct ata_port *port, unsigned drive,
+ uint64_t num)
{
- if (num > 0x0FFFFFFF || drive > 1)
+ struct ide_port *ide = to_ata_drive_access(port);
+
+ if (drive > 1)
return -EINVAL;
- ata_wr_byte(ide, 0xA0 | LBA_FLAG | drive << 4 | num >> 24,
- ide->io.device_addr);
- ata_wr_byte(ide, 0x00, ide->io.error_addr);
+ if (port->lba48) {
+ if (num > (1ULL << 48) - 1)
+ return -EINVAL;
+
+ ata_wr_byte(ide, LBA_FLAG | drive << 4, ide->io.device_addr);
+
+ ata_wr_byte(ide, 0x00, ide->io.nsect_addr);
+ ata_wr_byte(ide, num >> 24, ide->io.lbal_addr);
+ ata_wr_byte(ide, num >> 32, ide->io.lbam_addr);
+ ata_wr_byte(ide, num >> 40, ide->io.lbah_addr);
+ } else {
+ if (num > (1ULL << 28) - 1)
+ return -EINVAL;
+
+ ata_wr_byte(ide, 0xA0 | LBA_FLAG | drive << 4 | num >> 24,
+ ide->io.device_addr);
+ }
+
ata_wr_byte(ide, 0x01, ide->io.nsect_addr);
ata_wr_byte(ide, num, ide->io.lbal_addr); /* 0 ... 7 */
ata_wr_byte(ide, num >> 8, ide->io.lbam_addr); /* 8 ... 15 */
@@ -316,10 +332,18 @@ static int ide_read(struct ata_port *port, void *buffer, unsigned int block,
struct ide_port *ide = to_ata_drive_access(port);
while (num_blocks) {
- rc = ata_set_lba_sector(ide, DISK_MASTER, sector);
+ uint8_t cmd;
+
+ rc = ata_set_lba_sector(port, DISK_MASTER, sector);
if (rc != 0)
return rc;
- rc = ata_wr_cmd(ide, ATA_CMD_READ);
+
+ if (port->lba48)
+ cmd = ATA_CMD_PIO_READ_EXT;
+ else
+ cmd = ATA_CMD_READ;
+
+ rc = ata_wr_cmd(ide, cmd);
if (rc != 0)
return rc;
rc = ata_wait_ready(ide, MAX_TIMEOUT);
@@ -355,10 +379,18 @@ static int __maybe_unused ide_write(struct ata_port *port,
struct ide_port *ide = to_ata_drive_access(port);
while (num_blocks) {
- rc = ata_set_lba_sector(ide, DISK_MASTER, sector);
+ uint8_t cmd;
+
+ rc = ata_set_lba_sector(port, DISK_MASTER, sector);
if (rc != 0)
return rc;
- rc = ata_wr_cmd(ide, ATA_CMD_WRITE);
+
+ if (port->lba48)
+ cmd = ATA_CMD_PIO_WRITE_EXT;
+ else
+ cmd = ATA_CMD_WRITE;
+
+ rc = ata_wr_cmd(ide, cmd);
if (rc != 0)
return rc;
rc = ata_wait_ready(ide, MAX_TIMEOUT);
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index 5867fe45d0..83260990af 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -221,7 +221,7 @@ int unregister_device(struct device_d *old_dev)
}
list_for_each_entry_safe(cdev, ct, &old_dev->cdevs, devices_list) {
- if (cdev->flags & DEVFS_IS_PARTITION) {
+ if (cdev->master) {
dev_dbg(old_dev, "unregister part %s\n", cdev->name);
devfs_del_partition(cdev->name);
}
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 5811d28b88..d75b954a4e 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-$(CONFIG_CLK_SOCFPGA) += socfpga.o
obj-$(CONFIG_MACH_MIPS_ATH79) += clk-ar933x.o
obj-$(CONFIG_ARCH_IMX) += imx/
+obj-$(CONFIG_COMMON_CLK_AT91) += at91/
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
new file mode 100644
index 0000000000..13e67bd35c
--- /dev/null
+++ b/drivers/clk/at91/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for at91 specific clk
+#
+
+obj-y += pmc.o sckc.o
+obj-y += clk-slow.o clk-main.o clk-pll.o clk-plldiv.o clk-master.o
+obj-y += clk-system.o clk-peripheral.o clk-programmable.o
+
+obj-$(CONFIG_HAVE_AT91_UTMI) += clk-utmi.o
+obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o
+obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o
+obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o
+obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o
diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c
new file mode 100644
index 0000000000..4e1cd5aa69
--- /dev/null
+++ b/drivers/clk/at91/clk-generated.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2015 Atmel Corporation,
+ * Nicolas Ferre <nicolas.ferre@atmel.com>
+ *
+ * Based on clk-programmable & clk-peripheral drivers by Boris BREZILLON.
+ *
+ * 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/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define PERIPHERAL_MAX 64
+#define PERIPHERAL_ID_MIN 2
+
+#define GENERATED_SOURCE_MAX 6
+#define GENERATED_MAX_DIV 255
+
+struct clk_generated {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct clk_range range;
+ spinlock_t *lock;
+ u32 id;
+ u32 gckdiv;
+ u8 parent_id;
+};
+
+#define to_clk_generated(hw) \
+ container_of(hw, struct clk_generated, hw)
+
+static int clk_generated_enable(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ unsigned long flags;
+
+ pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n",
+ __func__, gck->gckdiv, gck->parent_id);
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, AT91_PMC_PCR,
+ (gck->id & AT91_PMC_PCR_PID_MASK));
+ regmap_update_bits(gck->regmap, AT91_PMC_PCR,
+ AT91_PMC_PCR_GCKDIV_MASK | AT91_PMC_PCR_GCKCSS_MASK |
+ AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN,
+ AT91_PMC_PCR_GCKCSS(gck->parent_id) |
+ AT91_PMC_PCR_CMD |
+ AT91_PMC_PCR_GCKDIV(gck->gckdiv) |
+ AT91_PMC_PCR_GCKEN);
+ spin_unlock_irqrestore(gck->lock, flags);
+ return 0;
+}
+
+static void clk_generated_disable(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, AT91_PMC_PCR,
+ (gck->id & AT91_PMC_PCR_PID_MASK));
+ regmap_update_bits(gck->regmap, AT91_PMC_PCR,
+ AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN,
+ AT91_PMC_PCR_CMD);
+ spin_unlock_irqrestore(gck->lock, flags);
+}
+
+static int clk_generated_is_enabled(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ unsigned long flags;
+ unsigned int status;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, AT91_PMC_PCR,
+ (gck->id & AT91_PMC_PCR_PID_MASK));
+ regmap_read(gck->regmap, AT91_PMC_PCR, &status);
+ spin_unlock_irqrestore(gck->lock, flags);
+
+ return status & AT91_PMC_PCR_GCKEN ? 1 : 0;
+}
+
+static unsigned long
+clk_generated_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ return DIV_ROUND_CLOSEST(parent_rate, gck->gckdiv + 1);
+}
+
+static int clk_generated_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ struct clk_hw *parent = NULL;
+ long best_rate = -EINVAL;
+ unsigned long tmp_rate, min_rate;
+ int best_diff = -1;
+ int tmp_diff;
+ int i;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ u32 div;
+ unsigned long parent_rate;
+
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+ min_rate = DIV_ROUND_CLOSEST(parent_rate, GENERATED_MAX_DIV + 1);
+ if (!parent_rate ||
+ (gck->range.max && min_rate > gck->range.max))
+ continue;
+
+ for (div = 1; div < GENERATED_MAX_DIV + 2; div++) {
+ tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div);
+ tmp_diff = abs(req->rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+
+ if (!best_diff || tmp_rate < req->rate)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ pr_debug("GCLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
+ __func__, best_rate,
+ __clk_get_name((req->best_parent_hw)->clk),
+ req->best_parent_rate);
+
+ if (best_rate < 0)
+ return best_rate;
+
+ req->rate = best_rate;
+ return 0;
+}
+
+/* No modification of hardware as we have the flag CLK_SET_PARENT_GATE set */
+static int clk_generated_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ if (index >= clk_hw_get_num_parents(hw))
+ return -EINVAL;
+
+ gck->parent_id = index;
+ return 0;
+}
+
+static u8 clk_generated_get_parent(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ return gck->parent_id;
+}
+
+/* No modification of hardware as we have the flag CLK_SET_RATE_GATE set */
+static int clk_generated_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ u32 div;
+
+ if (!rate)
+ return -EINVAL;
+
+ if (gck->range.max && rate > gck->range.max)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > GENERATED_MAX_DIV + 1 || !div)
+ return -EINVAL;
+
+ gck->gckdiv = div - 1;
+ return 0;
+}
+
+static const struct clk_ops generated_ops = {
+ .enable = clk_generated_enable,
+ .disable = clk_generated_disable,
+ .is_enabled = clk_generated_is_enabled,
+ .recalc_rate = clk_generated_recalc_rate,
+ .determine_rate = clk_generated_determine_rate,
+ .get_parent = clk_generated_get_parent,
+ .set_parent = clk_generated_set_parent,
+ .set_rate = clk_generated_set_rate,
+};
+
+/**
+ * clk_generated_startup - Initialize a given clock to its default parent and
+ * divisor parameter.
+ *
+ * @gck: Generated clock to set the startup parameters for.
+ *
+ * Take parameters from the hardware and update local clock configuration
+ * accordingly.
+ */
+static void clk_generated_startup(struct clk_generated *gck)
+{
+ u32 tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, AT91_PMC_PCR,
+ (gck->id & AT91_PMC_PCR_PID_MASK));
+ regmap_read(gck->regmap, AT91_PMC_PCR, &tmp);
+ spin_unlock_irqrestore(gck->lock, flags);
+
+ gck->parent_id = (tmp & AT91_PMC_PCR_GCKCSS_MASK)
+ >> AT91_PMC_PCR_GCKCSS_OFFSET;
+ gck->gckdiv = (tmp & AT91_PMC_PCR_GCKDIV_MASK)
+ >> AT91_PMC_PCR_GCKDIV_OFFSET;
+}
+
+static struct clk_hw * __init
+at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
+ const char *name, const char **parent_names,
+ u8 num_parents, u8 id,
+ const struct clk_range *range)
+{
+ struct clk_generated *gck;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ gck = kzalloc(sizeof(*gck), GFP_KERNEL);
+ if (!gck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &generated_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ gck->id = id;
+ gck->hw.init = &init;
+ gck->regmap = regmap;
+ gck->lock = lock;
+ gck->range = *range;
+
+ hw = &gck->hw;
+ ret = clk_hw_register(NULL, &gck->hw);
+ if (ret) {
+ kfree(gck);
+ hw = ERR_PTR(ret);
+ } else
+ clk_generated_startup(gck);
+
+ return hw;
+}
+
+static void __init of_sama5d2_clk_generated_setup(struct device_node *np)
+{
+ int num;
+ u32 id;
+ const char *name;
+ struct clk_hw *hw;
+ unsigned int num_parents;
+ const char *parent_names[GENERATED_SOURCE_MAX];
+ struct device_node *gcknp;
+ struct clk_range range = CLK_RANGE(0, 0);
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > GENERATED_SOURCE_MAX)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ num = of_get_child_count(np);
+ if (!num || num > PERIPHERAL_MAX)
+ return;
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return;
+
+ for_each_child_of_node(np, gcknp) {
+ if (of_property_read_u32(gcknp, "reg", &id))
+ continue;
+
+ if (id < PERIPHERAL_ID_MIN || id >= PERIPHERAL_MAX)
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = gcknp->name;
+
+ of_at91_get_clk_range(gcknp, "atmel,clk-output-range",
+ &range);
+
+ hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, name,
+ parent_names, num_parents,
+ id, &range);
+ if (IS_ERR(hw))
+ continue;
+
+ of_clk_add_hw_provider(gcknp, of_clk_hw_simple_get, hw);
+ }
+}
+CLK_OF_DECLARE(of_sama5d2_clk_generated_setup, "atmel,sama5d2-clk-generated",
+ of_sama5d2_clk_generated_setup);
diff --git a/drivers/clk/at91/clk-h32mx.c b/drivers/clk/at91/clk-h32mx.c
new file mode 100644
index 0000000000..e0daa4a31f
--- /dev/null
+++ b/drivers/clk/at91/clk-h32mx.c
@@ -0,0 +1,125 @@
+/*
+ * clk-h32mx.c
+ *
+ * Copyright (C) 2014 Atmel
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * 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/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#include "pmc.h"
+
+#define H32MX_MAX_FREQ 90000000
+
+struct clk_sama5d4_h32mx {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+#define to_clk_sama5d4_h32mx(hw) container_of(hw, struct clk_sama5d4_h32mx, hw)
+
+static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw);
+ unsigned int mckr;
+
+ regmap_read(h32mxclk->regmap, AT91_PMC_MCKR, &mckr);
+ if (mckr & AT91_PMC_H32MXDIV)
+ return parent_rate / 2;
+
+ if (parent_rate > H32MX_MAX_FREQ)
+ pr_warn("H32MX clock is too fast\n");
+ return parent_rate;
+}
+
+static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+
+ if (rate > *parent_rate)
+ return *parent_rate;
+ div = *parent_rate / 2;
+ if (rate < div)
+ return div;
+
+ if (rate - div < *parent_rate - rate)
+ return div;
+
+ return *parent_rate;
+}
+
+static int clk_sama5d4_h32mx_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw);
+ u32 mckr = 0;
+
+ if (parent_rate != rate && (parent_rate / 2) != rate)
+ return -EINVAL;
+
+ if ((parent_rate / 2) == rate)
+ mckr = AT91_PMC_H32MXDIV;
+
+ regmap_update_bits(h32mxclk->regmap, AT91_PMC_MCKR,
+ AT91_PMC_H32MXDIV, mckr);
+
+ return 0;
+}
+
+static const struct clk_ops h32mx_ops = {
+ .recalc_rate = clk_sama5d4_h32mx_recalc_rate,
+ .round_rate = clk_sama5d4_h32mx_round_rate,
+ .set_rate = clk_sama5d4_h32mx_set_rate,
+};
+
+static void __init of_sama5d4_clk_h32mx_setup(struct device_node *np)
+{
+ struct clk_sama5d4_h32mx *h32mxclk;
+ struct clk_init_data init;
+ const char *parent_name;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return;
+
+ h32mxclk = kzalloc(sizeof(*h32mxclk), GFP_KERNEL);
+ if (!h32mxclk)
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ init.name = np->name;
+ init.ops = &h32mx_ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = CLK_SET_RATE_GATE;
+
+ h32mxclk->hw.init = &init;
+ h32mxclk->regmap = regmap;
+
+ ret = clk_hw_register(NULL, &h32mxclk->hw);
+ if (ret) {
+ kfree(h32mxclk);
+ return;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, &h32mxclk->hw);
+}
+CLK_OF_DECLARE(of_sama5d4_clk_h32mx_setup, "atmel,sama5d4-clk-h32mx",
+ of_sama5d4_clk_h32mx_setup);
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
new file mode 100644
index 0000000000..55bc618a37
--- /dev/null
+++ b/drivers/clk/at91/clk-main.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define SLOW_CLOCK_FREQ 32768
+#define MAINF_DIV 16
+#define MAINFRDY_TIMEOUT (((MAINF_DIV + 1) * SECOND) / \
+ SLOW_CLOCK_FREQ)
+#define MAINF_LOOP_MIN_WAIT (SECOND / SLOW_CLOCK_FREQ)
+#define MAINF_LOOP_MAX_WAIT MAINFRDY_TIMEOUT
+
+#define MOR_KEY_MASK (0xff << 16)
+
+struct clk_main_osc {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent;
+};
+
+#define to_clk_main_osc(clk) container_of(clk, struct clk_main_osc, clk)
+
+struct clk_main_rc_osc {
+ struct clk clk;
+ struct regmap *regmap;
+ unsigned long frequency;
+};
+
+#define to_clk_main_rc_osc(clk) container_of(clk, struct clk_main_rc_osc, clk)
+
+struct clk_rm9200_main {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent;
+};
+
+#define to_clk_rm9200_main(clk) container_of(clk, struct clk_rm9200_main, clk)
+
+struct clk_sam9x5_main {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 parent;
+};
+
+#define to_clk_sam9x5_main(clk) container_of(clk, struct clk_sam9x5_main, clk)
+
+static inline bool clk_main_osc_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_MOSCS;
+}
+
+static int clk_main_osc_enable(struct clk *clk)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(clk);
+ struct regmap *regmap = osc->regmap;
+ u32 tmp;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ tmp &= ~MOR_KEY_MASK;
+
+ if (tmp & AT91_PMC_OSCBYPASS)
+ return 0;
+
+ if (!(tmp & AT91_PMC_MOSCEN)) {
+ tmp |= AT91_PMC_MOSCEN | AT91_PMC_KEY;
+ regmap_write(regmap, AT91_CKGR_MOR, tmp);
+ }
+
+ while (!clk_main_osc_ready(regmap))
+ barrier();
+
+ return 0;
+}
+
+static void clk_main_osc_disable(struct clk *clk)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(clk);
+ struct regmap *regmap = osc->regmap;
+ u32 tmp;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ if (tmp & AT91_PMC_OSCBYPASS)
+ return;
+
+ if (!(tmp & AT91_PMC_MOSCEN))
+ return;
+
+ tmp &= ~(AT91_PMC_KEY | AT91_PMC_MOSCEN);
+ regmap_write(regmap, AT91_CKGR_MOR, tmp | AT91_PMC_KEY);
+}
+
+static int clk_main_osc_is_enabled(struct clk *clk)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(clk);
+ struct regmap *regmap = osc->regmap;
+ u32 tmp, status;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ if (tmp & AT91_PMC_OSCBYPASS)
+ return 1;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return (status & AT91_PMC_MOSCS) && (tmp & AT91_PMC_MOSCEN);
+}
+
+static const struct clk_ops main_osc_ops = {
+ .enable = clk_main_osc_enable,
+ .disable = clk_main_osc_disable,
+ .is_enabled = clk_main_osc_is_enabled,
+};
+
+static struct clk *
+at91_clk_register_main_osc(struct regmap *regmap,
+ const char *name,
+ const char *parent_name,
+ bool bypass)
+{
+ struct clk_main_osc *osc;
+ int ret;
+
+ if (!name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ osc = xzalloc(sizeof(*osc));
+
+ osc->parent = parent_name;
+ osc->clk.name = name;
+ osc->clk.ops = &main_osc_ops;
+ osc->clk.parent_names = &osc->parent;
+ osc->clk.num_parents = 1;
+ osc->regmap = regmap;
+
+ if (bypass)
+ regmap_write_bits(regmap,
+ AT91_CKGR_MOR, MOR_KEY_MASK |
+ AT91_PMC_MOSCEN,
+ AT91_PMC_OSCBYPASS | AT91_PMC_KEY);
+
+ ret = clk_register(&osc->clk);
+ if (ret) {
+ free(osc);
+ return ERR_PTR(ret);
+ }
+
+ return &osc->clk;
+}
+
+static int of_at91rm9200_clk_main_osc_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *name = np->name;
+ const char *parent_name;
+ struct regmap *regmap;
+ bool bypass;
+
+ of_property_read_string(np, "clock-output-names", &name);
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91_clk_register_main_osc(regmap, name, parent_name, bypass);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91rm9200_clk_main_osc, "atmel,at91rm9200-clk-main-osc",
+ of_at91rm9200_clk_main_osc_setup);
+
+static bool clk_main_rc_osc_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_MOSCRCS;
+}
+
+static int clk_main_rc_osc_enable(struct clk *clk)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk);
+ struct regmap *regmap = osc->regmap;
+ unsigned int mor;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &mor);
+
+ if (!(mor & AT91_PMC_MOSCRCEN))
+ regmap_write_bits(regmap, AT91_CKGR_MOR,
+ MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
+ AT91_PMC_MOSCRCEN | AT91_PMC_KEY);
+
+ while (!clk_main_rc_osc_ready(regmap))
+ barrier();
+
+ return 0;
+}
+
+static void clk_main_rc_osc_disable(struct clk *clk)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk);
+ struct regmap *regmap = osc->regmap;
+ unsigned int mor;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &mor);
+
+ if (!(mor & AT91_PMC_MOSCRCEN))
+ return;
+
+ regmap_write_bits(regmap, AT91_CKGR_MOR,
+ MOR_KEY_MASK | AT91_PMC_MOSCRCEN, AT91_PMC_KEY);
+}
+
+static int clk_main_rc_osc_is_enabled(struct clk *clk)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk);
+ struct regmap *regmap = osc->regmap;
+ unsigned int mor, status;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &mor);
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return (mor & AT91_PMC_MOSCRCEN) && (status & AT91_PMC_MOSCRCS);
+}
+
+static unsigned long clk_main_rc_osc_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk);
+
+ return osc->frequency;
+}
+
+static const struct clk_ops main_rc_osc_ops = {
+ .enable = clk_main_rc_osc_enable,
+ .disable = clk_main_rc_osc_disable,
+ .is_enabled = clk_main_rc_osc_is_enabled,
+ .recalc_rate = clk_main_rc_osc_recalc_rate,
+};
+
+static struct clk *
+at91_clk_register_main_rc_osc(struct regmap *regmap,
+ const char *name,
+ u32 frequency)
+{
+ int ret;
+ struct clk_main_rc_osc *osc;
+
+ if (!name || !frequency)
+ return ERR_PTR(-EINVAL);
+
+ osc = xzalloc(sizeof(*osc));
+
+ osc->clk.name = name;
+ osc->clk.ops = &main_rc_osc_ops;
+ osc->clk.parent_names = NULL;
+ osc->clk.num_parents = 0;
+
+ osc->regmap = regmap;
+ osc->frequency = frequency;
+
+ ret = clk_register(&osc->clk);
+ if (ret) {
+ kfree(osc);
+ return ERR_PTR(ret);
+ }
+
+ return &osc->clk;
+}
+
+static int of_at91sam9x5_clk_main_rc_osc_setup(struct device_node *np)
+{
+ struct clk *clk;
+ u32 frequency = 0;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ of_property_read_string(np, "clock-output-names", &name);
+ of_property_read_u32(np, "clock-frequency", &frequency);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91_clk_register_main_rc_osc(regmap, name, frequency);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_main_rc_osc, "atmel,at91sam9x5-clk-main-rc-osc",
+ of_at91sam9x5_clk_main_rc_osc_setup);
+
+
+static int clk_main_probe_frequency(struct regmap *regmap)
+{
+ unsigned int mcfr;
+ uint64_t start = get_time_ns();
+
+ do {
+ regmap_read(regmap, AT91_CKGR_MCFR, &mcfr);
+ if (mcfr & AT91_PMC_MAINRDY)
+ return 0;
+ } while (!is_timeout(start, MAINFRDY_TIMEOUT * USECOND));
+
+ return -ETIMEDOUT;
+}
+
+static unsigned long clk_main_recalc_rate(struct regmap *regmap,
+ unsigned long parent_rate)
+{
+ unsigned int mcfr;
+
+ if (parent_rate)
+ return parent_rate;
+
+ pr_warn("Main crystal frequency not set, using approximate value\n");
+ regmap_read(regmap, AT91_CKGR_MCFR, &mcfr);
+ if (!(mcfr & AT91_PMC_MAINRDY))
+ return 0;
+
+ return ((mcfr & AT91_PMC_MAINF) * SLOW_CLOCK_FREQ) / MAINF_DIV;
+}
+
+static int clk_rm9200_main_enable(struct clk *clk)
+{
+ struct clk_rm9200_main *clkmain = to_clk_rm9200_main(clk);
+
+ return clk_main_probe_frequency(clkmain->regmap);
+}
+
+static int clk_rm9200_main_is_enabled(struct clk *clk)
+{
+ struct clk_rm9200_main *clkmain = to_clk_rm9200_main(clk);
+ unsigned int status;
+
+ regmap_read(clkmain->regmap, AT91_CKGR_MCFR, &status);
+
+ return status & AT91_PMC_MAINRDY ? 1 : 0;
+}
+
+static unsigned long clk_rm9200_main_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_rm9200_main *clkmain = to_clk_rm9200_main(clk);
+
+ return clk_main_recalc_rate(clkmain->regmap, parent_rate);
+}
+
+static const struct clk_ops rm9200_main_ops = {
+ .enable = clk_rm9200_main_enable,
+ .is_enabled = clk_rm9200_main_is_enabled,
+ .recalc_rate = clk_rm9200_main_recalc_rate,
+};
+
+static struct clk *
+at91_clk_register_rm9200_main(struct regmap *regmap,
+ const char *name,
+ const char *parent_name)
+{
+ int ret;
+ struct clk_rm9200_main *clkmain;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ if (!parent_name)
+ return ERR_PTR(-EINVAL);
+
+ clkmain = xzalloc(sizeof(*clkmain));
+
+ clkmain->clk.name = name;
+ clkmain->clk.ops = &rm9200_main_ops;
+ clkmain->clk.parent_names = &clkmain->parent;
+ clkmain->clk.num_parents = 1;
+ clkmain->regmap = regmap;
+
+ ret = clk_register(&clkmain->clk);
+ if (ret) {
+ kfree(clkmain);
+ return ERR_PTR(ret);
+ }
+
+ return &clkmain->clk;
+}
+
+static int of_at91rm9200_clk_main_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91_clk_register_rm9200_main(regmap, name, parent_name);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91rm9200_clk_main, "atmel,at91rm9200-clk-main",
+ of_at91rm9200_clk_main_setup);
+
+static inline bool clk_sam9x5_main_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_MOSCSELS ? 1 : 0;
+}
+
+static int clk_sam9x5_main_enable(struct clk *clk)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk);
+ struct regmap *regmap = clkmain->regmap;
+
+ while (!clk_sam9x5_main_ready(regmap))
+ barrier();
+
+ return clk_main_probe_frequency(regmap);
+}
+
+static int clk_sam9x5_main_is_enabled(struct clk *clk)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk);
+
+ return clk_sam9x5_main_ready(clkmain->regmap);
+}
+
+static unsigned long clk_sam9x5_main_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk);
+
+ return clk_main_recalc_rate(clkmain->regmap, parent_rate);
+}
+
+static int clk_sam9x5_main_set_parent(struct clk *clk, u8 index)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk);
+ struct regmap *regmap = clkmain->regmap;
+ unsigned int tmp;
+
+ if (index > 1)
+ return -EINVAL;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ tmp &= ~MOR_KEY_MASK;
+
+ if (index && !(tmp & AT91_PMC_MOSCSEL))
+ regmap_write(regmap, AT91_CKGR_MOR, tmp | AT91_PMC_MOSCSEL);
+ else if (!index && (tmp & AT91_PMC_MOSCSEL))
+ regmap_write(regmap, AT91_CKGR_MOR, tmp & ~AT91_PMC_MOSCSEL);
+
+ while (!clk_sam9x5_main_ready(regmap))
+ barrier();
+
+ return 0;
+}
+
+static int clk_sam9x5_main_get_parent(struct clk *clk)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk);
+ unsigned int status;
+
+ regmap_read(clkmain->regmap, AT91_CKGR_MOR, &status);
+
+ return status & AT91_PMC_MOSCEN ? 1 : 0;
+}
+
+static const struct clk_ops sam9x5_main_ops = {
+ .enable = clk_sam9x5_main_enable,
+ .is_enabled = clk_sam9x5_main_is_enabled,
+ .recalc_rate = clk_sam9x5_main_recalc_rate,
+ .set_parent = clk_sam9x5_main_set_parent,
+ .get_parent = clk_sam9x5_main_get_parent,
+};
+
+static struct clk *
+at91_clk_register_sam9x5_main(struct regmap *regmap,
+ const char *name,
+ const char **parent_names,
+ int num_parents)
+{
+ int ret;
+ unsigned int status;
+ size_t parents_array_size;
+ struct clk_sam9x5_main *clkmain;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ if (!parent_names || !num_parents)
+ return ERR_PTR(-EINVAL);
+
+ clkmain = xzalloc(sizeof(*clkmain));
+
+ clkmain->clk.name = name;
+ clkmain->clk.ops = &sam9x5_main_ops;
+ parents_array_size = num_parents * sizeof (clkmain->clk.parent_names[0]);
+ clkmain->clk.parent_names = xzalloc(parents_array_size);
+ memcpy(clkmain->clk.parent_names, parent_names, parents_array_size);
+ clkmain->clk.num_parents = num_parents;
+
+ /* init.flags = CLK_SET_PARENT_GATE; */
+
+ clkmain->regmap = regmap;
+ regmap_read(clkmain->regmap, AT91_CKGR_MOR, &status);
+ clkmain->parent = status & AT91_PMC_MOSCEN ? 1 : 0;
+
+ ret = clk_register(&clkmain->clk);
+ if (ret) {
+ kfree(clkmain);
+ return ERR_PTR(ret);
+ }
+
+ return &clkmain->clk;
+}
+
+static int of_at91sam9x5_clk_main_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_names[2];
+ unsigned int num_parents;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > 2)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91_clk_register_sam9x5_main(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_main, "atmel,at91sam9x5-clk-main",
+ of_at91sam9x5_clk_main_setup);
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
new file mode 100644
index 0000000000..b3a50ce542
--- /dev/null
+++ b/drivers/clk/at91/clk-master.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define MASTER_SOURCE_MAX 4
+
+#define MASTER_PRES_MASK 0x7
+#define MASTER_PRES_MAX MASTER_PRES_MASK
+#define MASTER_DIV_SHIFT 8
+#define MASTER_DIV_MASK 0x3
+
+struct clk_master_characteristics {
+ struct clk_range output;
+ u32 divisors[4];
+ u8 have_div3_pres;
+};
+
+struct clk_master_layout {
+ u32 mask;
+ u8 pres_shift;
+};
+
+#define to_clk_master(clk) container_of(clk, struct clk_master, clk)
+
+struct clk_master {
+ struct clk clk;
+ struct regmap *regmap;
+ const struct clk_master_layout *layout;
+ const struct clk_master_characteristics *characteristics;
+ const char *parents[MASTER_SOURCE_MAX];
+};
+
+static inline bool clk_master_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_MCKRDY ? 1 : 0;
+}
+
+static int clk_master_enable(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+
+ while (!clk_master_ready(master->regmap))
+ barrier();
+
+ return 0;
+}
+
+static int clk_master_is_enabled(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+
+ return clk_master_ready(master->regmap);
+}
+
+static unsigned long clk_master_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ u8 pres;
+ u8 div;
+ unsigned long rate = parent_rate;
+ struct clk_master *master = to_clk_master(clk);
+ const struct clk_master_layout *layout = master->layout;
+ const struct clk_master_characteristics *characteristics =
+ master->characteristics;
+ unsigned int mckr;
+
+ regmap_read(master->regmap, AT91_PMC_MCKR, &mckr);
+ mckr &= layout->mask;
+
+ pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+ if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
+ rate /= 3;
+ else
+ rate >>= pres;
+
+ rate /= characteristics->divisors[div];
+
+ if (rate < characteristics->output.min)
+ pr_warn("master clk is underclocked");
+ else if (rate > characteristics->output.max)
+ pr_warn("master clk is overclocked");
+
+ return rate;
+}
+
+static int clk_master_get_parent(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+ unsigned int mckr;
+
+ regmap_read(master->regmap, AT91_PMC_MCKR, &mckr);
+
+ return mckr & AT91_PMC_CSS;
+}
+
+static const struct clk_ops master_ops = {
+ .enable = clk_master_enable,
+ .is_enabled = clk_master_is_enabled,
+ .recalc_rate = clk_master_recalc_rate,
+ .get_parent = clk_master_get_parent,
+};
+
+static struct clk *
+at91_clk_register_master(struct regmap *regmap,
+ const char *name, int num_parents,
+ const char **parent_names,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics)
+{
+ int ret;
+ const size_t parent_names_size = num_parents * sizeof(parent_names[0]);
+ struct clk_master *master;
+
+ if (!name || !num_parents || !parent_names)
+ return ERR_PTR(-EINVAL);
+
+ master = xzalloc(sizeof(*master));
+
+ master->clk.name = name;
+ master->clk.ops = &master_ops;
+ memcpy(master->parents, parent_names, parent_names_size);
+ master->clk.parent_names = master->parents;
+ master->clk.num_parents = num_parents;
+
+ master->layout = layout;
+ master->characteristics = characteristics;
+ master->regmap = regmap;
+
+ ret = clk_register(&master->clk);
+ if (ret) {
+ kfree(master);
+ return ERR_PTR(ret);
+ }
+
+ return &master->clk;
+}
+
+
+static const struct clk_master_layout at91rm9200_master_layout = {
+ .mask = 0x31F,
+ .pres_shift = 2,
+};
+
+static const struct clk_master_layout at91sam9x5_master_layout = {
+ .mask = 0x373,
+ .pres_shift = 4,
+};
+
+
+static struct clk_master_characteristics *
+of_at91_clk_master_get_characteristics(struct device_node *np)
+{
+ struct clk_master_characteristics *characteristics;
+
+ characteristics = xzalloc(sizeof(*characteristics));
+
+ if (of_at91_get_clk_range(np, "atmel,clk-output-range", &characteristics->output))
+ goto out_free_characteristics;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors",
+ characteristics->divisors, 4);
+
+ characteristics->have_div3_pres =
+ of_property_read_bool(np, "atmel,master-clk-have-div3-pres");
+
+ return characteristics;
+
+out_free_characteristics:
+ kfree(characteristics);
+ return NULL;
+}
+
+static int
+of_at91_clk_master_setup(struct device_node *np,
+ const struct clk_master_layout *layout)
+{
+ struct clk *clk;
+ unsigned int num_parents;
+ const char *parent_names[MASTER_SOURCE_MAX];
+ const char *name = np->name;
+ struct clk_master_characteristics *characteristics;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > MASTER_SOURCE_MAX)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ characteristics = of_at91_clk_master_get_characteristics(np);
+ if (!characteristics)
+ return -EINVAL;
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91_clk_register_master(regmap, name, num_parents,
+ parent_names, layout,
+ characteristics);
+ if (IS_ERR(clk)) {
+ kfree(characteristics);
+ return PTR_ERR(clk);
+ }
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+static void __init of_at91rm9200_clk_master_setup(struct device_node *np)
+{
+ of_at91_clk_master_setup(np, &at91rm9200_master_layout);
+}
+CLK_OF_DECLARE(at91rm9200_clk_master, "atmel,at91rm9200-clk-master",
+ of_at91rm9200_clk_master_setup);
+
+static void __init of_at91sam9x5_clk_master_setup(struct device_node *np)
+{
+ of_at91_clk_master_setup(np, &at91sam9x5_master_layout);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_master, "atmel,at91sam9x5-clk-master",
+ of_at91sam9x5_clk_master_setup);
diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c
new file mode 100644
index 0000000000..bbe6ffac69
--- /dev/null
+++ b/drivers/clk/at91/clk-peripheral.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define PERIPHERAL_MAX 64
+
+#define PERIPHERAL_AT91RM9200 0
+#define PERIPHERAL_AT91SAM9X5 1
+
+#define PERIPHERAL_ID_MIN 2
+#define PERIPHERAL_ID_MAX 31
+#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
+
+#define PERIPHERAL_RSHIFT_MASK 0x3
+#define PERIPHERAL_RSHIFT(val) (((val) >> 16) & PERIPHERAL_RSHIFT_MASK)
+
+#define PERIPHERAL_MAX_SHIFT 3
+
+struct clk_peripheral {
+ struct clk clk;
+ struct regmap *regmap;
+ u32 id;
+ const char *parent;
+};
+
+#define to_clk_peripheral(clk) container_of(clk, struct clk_peripheral, clk)
+
+struct clk_sam9x5_peripheral {
+ struct clk clk;
+ struct regmap *regmap;
+ struct clk_range range;
+ u32 id;
+ u32 div;
+ bool auto_div;
+ const char *parent;
+};
+
+#define to_clk_sam9x5_peripheral(clk) \
+ container_of(clk, struct clk_sam9x5_peripheral, clk)
+
+static int clk_peripheral_enable(struct clk *clk)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(clk);
+ int offset = AT91_PMC_PCER;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return 0;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCER1;
+ regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
+
+ return 0;
+}
+
+static void clk_peripheral_disable(struct clk *clk)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(clk);
+ int offset = AT91_PMC_PCDR;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCDR1;
+ regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
+}
+
+static int clk_peripheral_is_enabled(struct clk *clk)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(clk);
+ int offset = AT91_PMC_PCSR;
+ unsigned int status;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return 1;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCSR1;
+ regmap_read(periph->regmap, offset, &status);
+
+ return status & PERIPHERAL_MASK(id) ? 1 : 0;
+}
+
+static const struct clk_ops peripheral_ops = {
+ .enable = clk_peripheral_enable,
+ .disable = clk_peripheral_disable,
+ .is_enabled = clk_peripheral_is_enabled,
+};
+
+static struct clk *
+at91_clk_register_peripheral(struct regmap *regmap, const char *name,
+ const char *parent_name, u32 id)
+{
+ int ret;
+ struct clk_peripheral *periph;
+
+ if (!name || !parent_name || id > PERIPHERAL_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ periph = xzalloc(sizeof(*periph));
+
+ periph->clk.name = name;
+ periph->clk.ops = &peripheral_ops;
+
+ if (parent_name) {
+ periph->parent = parent_name;
+ periph->clk.parent_names = &periph->parent;
+ periph->clk.num_parents = 1;
+ }
+
+ periph->id = id;
+ periph->regmap = regmap;
+
+ ret = clk_register(&periph->clk);
+ if (ret) {
+ kfree(periph);
+ return ERR_PTR(ret);
+ }
+
+ return &periph->clk;
+}
+
+static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
+{
+ struct clk *parent;
+ unsigned long parent_rate;
+ int shift = 0;
+
+ if (!periph->auto_div)
+ return;
+
+ if (periph->range.max) {
+ parent = clk_get_parent(&periph->clk);
+ parent_rate = clk_get_rate(parent);
+ if (!parent_rate)
+ return;
+
+ for (; shift < PERIPHERAL_MAX_SHIFT; shift++) {
+ if (parent_rate >> shift <= periph->range.max)
+ break;
+ }
+ }
+
+ periph->auto_div = false;
+ periph->div = shift;
+}
+
+static int clk_sam9x5_peripheral_enable(struct clk *clk)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 0;
+
+ regmap_write(periph->regmap, AT91_PMC_PCR,
+ (periph->id & AT91_PMC_PCR_PID_MASK));
+ regmap_write_bits(periph->regmap, AT91_PMC_PCR,
+ AT91_PMC_PCR_DIV_MASK | AT91_PMC_PCR_CMD |
+ AT91_PMC_PCR_EN,
+ AT91_PMC_PCR_DIV(periph->div) |
+ AT91_PMC_PCR_CMD |
+ AT91_PMC_PCR_EN);
+
+ return 0;
+}
+
+static void clk_sam9x5_peripheral_disable(struct clk *clk)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return;
+
+ regmap_write(periph->regmap, AT91_PMC_PCR,
+ (periph->id & AT91_PMC_PCR_PID_MASK));
+ regmap_write_bits(periph->regmap, AT91_PMC_PCR,
+ AT91_PMC_PCR_EN | AT91_PMC_PCR_CMD,
+ AT91_PMC_PCR_CMD);
+}
+
+static int clk_sam9x5_peripheral_is_enabled(struct clk *clk)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+ unsigned int status;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 1;
+
+ regmap_write(periph->regmap, AT91_PMC_PCR,
+ (periph->id & AT91_PMC_PCR_PID_MASK));
+ regmap_read(periph->regmap, AT91_PMC_PCR, &status);
+
+ return status & AT91_PMC_PCR_EN ? 1 : 0;
+}
+
+static unsigned long
+clk_sam9x5_peripheral_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+ unsigned int status;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return parent_rate;
+
+ regmap_write(periph->regmap, AT91_PMC_PCR,
+ (periph->id & AT91_PMC_PCR_PID_MASK));
+ regmap_read(periph->regmap, AT91_PMC_PCR, &status);
+
+ if (status & AT91_PMC_PCR_EN) {
+ periph->div = PERIPHERAL_RSHIFT(status);
+ periph->auto_div = false;
+ } else {
+ clk_sam9x5_peripheral_autodiv(periph);
+ }
+
+ return parent_rate >> periph->div;
+}
+
+static long clk_sam9x5_peripheral_round_rate(struct clk *clk,
+ unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int shift = 0;
+ unsigned long best_rate;
+ unsigned long best_diff;
+ unsigned long cur_rate = *parent_rate;
+ unsigned long cur_diff;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
+ return *parent_rate;
+
+ if (periph->range.max) {
+ for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ cur_rate = *parent_rate >> shift;
+ if (cur_rate <= periph->range.max)
+ break;
+ }
+ }
+
+ if (rate >= cur_rate)
+ return cur_rate;
+
+ best_diff = cur_rate - rate;
+ best_rate = cur_rate;
+ for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ cur_rate = *parent_rate >> shift;
+ if (cur_rate < rate)
+ cur_diff = rate - cur_rate;
+ else
+ cur_diff = cur_rate - rate;
+
+ if (cur_diff < best_diff) {
+ best_diff = cur_diff;
+ best_rate = cur_rate;
+ }
+
+ if (!best_diff || cur_rate < rate)
+ break;
+ }
+
+ return best_rate;
+}
+
+static int clk_sam9x5_peripheral_set_rate(struct clk *clk,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ int shift;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
+ if (parent_rate == rate)
+ return 0;
+ else
+ return -EINVAL;
+ }
+
+ if (periph->range.max && rate > periph->range.max)
+ return -EINVAL;
+
+ for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ if (parent_rate >> shift == rate) {
+ periph->auto_div = false;
+ periph->div = shift;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct clk_ops sam9x5_peripheral_ops = {
+ .enable = clk_sam9x5_peripheral_enable,
+ .disable = clk_sam9x5_peripheral_disable,
+ .is_enabled = clk_sam9x5_peripheral_is_enabled,
+ .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
+ .round_rate = clk_sam9x5_peripheral_round_rate,
+ .set_rate = clk_sam9x5_peripheral_set_rate,
+};
+
+static struct clk *
+at91_clk_register_sam9x5_peripheral(struct regmap *regmap,
+ const char *name, const char *parent_name,
+ u32 id, const struct clk_range *range)
+{
+ int ret;
+ struct clk_sam9x5_peripheral *periph;
+
+ if (!name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ periph = xzalloc(sizeof(*periph));
+
+ periph->clk.name = name;
+ periph->clk.ops = &sam9x5_peripheral_ops;
+
+ if (parent_name) {
+ periph->parent = parent_name;
+ periph->clk.parent_names = &periph->parent;
+ periph->clk.num_parents = 1;
+ }
+
+ periph->id = id;
+ periph->div = 0;
+ periph->regmap = regmap;
+ periph->auto_div = true;
+ periph->range = *range;
+
+ ret = clk_register(&periph->clk);
+ if (ret) {
+ kfree(periph);
+ return ERR_PTR(ret);
+ }
+
+ clk_sam9x5_peripheral_autodiv(periph);
+
+ return &periph->clk;
+}
+
+static int
+of_at91_clk_periph_setup(struct device_node *np, u8 type)
+{
+ int num;
+ u32 id;
+ struct clk *clk;
+ const char *parent_name;
+ const char *name;
+ struct device_node *periphclknp;
+ struct regmap *regmap;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return -ENOENT;
+
+ num = of_get_child_count(np);
+ if (!num || num > PERIPHERAL_MAX)
+ return -EINVAL;
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ for_each_child_of_node(np, periphclknp) {
+ if (of_property_read_u32(periphclknp, "reg", &id))
+ continue;
+
+ if (id >= PERIPHERAL_MAX)
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = periphclknp->name;
+
+ if (type == PERIPHERAL_AT91RM9200) {
+ clk = at91_clk_register_peripheral(regmap, name,
+ parent_name, id);
+ } else {
+ struct clk_range range = CLK_RANGE(0, 0);
+
+ of_at91_get_clk_range(periphclknp,
+ "atmel,clk-output-range",
+ &range);
+
+ clk = at91_clk_register_sam9x5_peripheral(regmap,
+ name,
+ parent_name,
+ id, &range);
+ }
+
+ if (IS_ERR(clk))
+ continue;
+
+ of_clk_add_provider(periphclknp, of_clk_src_simple_get, clk);
+ }
+
+ return 0;
+}
+
+static int of_at91rm9200_clk_periph_setup(struct device_node *np)
+{
+ return of_at91_clk_periph_setup(np, PERIPHERAL_AT91RM9200);
+}
+CLK_OF_DECLARE(at91rm9200_clk_periph, "atmel,at91rm9200-clk-peripheral",
+ of_at91rm9200_clk_periph_setup);
+
+static int of_at91sam9x5_clk_periph_setup(struct device_node *np)
+{
+ return of_at91_clk_periph_setup(np, PERIPHERAL_AT91SAM9X5);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_periph, "atmel,at91sam9x5-clk-peripheral",
+ of_at91sam9x5_clk_periph_setup);
diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
new file mode 100644
index 0000000000..e0af4fe5a8
--- /dev/null
+++ b/drivers/clk/at91/clk-pll.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define PLL_STATUS_MASK(id) (1 << (1 + (id)))
+#define PLL_REG(id) (AT91_CKGR_PLLAR + ((id) * 4))
+#define PLL_DIV_MASK 0xff
+#define PLL_DIV_MAX PLL_DIV_MASK
+#define PLL_DIV(reg) ((reg) & PLL_DIV_MASK)
+#define PLL_MUL(reg, layout) (((reg) >> (layout)->mul_shift) & \
+ (layout)->mul_mask)
+#define PLL_MUL_MIN 2
+#define PLL_MUL_MASK(layout) ((layout)->mul_mask)
+#define PLL_MUL_MAX(layout) (PLL_MUL_MASK(layout) + 1)
+#define PLL_ICPR_SHIFT(id) ((id) * 16)
+#define PLL_ICPR_MASK(id) (0xffff << PLL_ICPR_SHIFT(id))
+#define PLL_MAX_COUNT 0x3f
+#define PLL_COUNT_SHIFT 8
+#define PLL_OUT_SHIFT 14
+#define PLL_MAX_ID 1
+
+struct clk_pll_characteristics {
+ struct clk_range input;
+ int num_output;
+ struct clk_range *output;
+ u16 *icpll;
+ u8 *out;
+};
+
+struct clk_pll_layout {
+ u32 pllr_mask;
+ u16 mul_mask;
+ u8 mul_shift;
+};
+
+#define to_clk_pll(clk) container_of(clk, struct clk_pll, clk)
+
+struct clk_pll {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 id;
+ u8 div;
+ u8 range;
+ u16 mul;
+ const struct clk_pll_layout *layout;
+ const struct clk_pll_characteristics *characteristics;
+ const char *parent;
+};
+
+static inline bool clk_pll_ready(struct regmap *regmap, int id)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & PLL_STATUS_MASK(id) ? 1 : 0;
+}
+
+static int clk_pll_enable(struct clk *clk)
+{
+ struct clk_pll *pll = to_clk_pll(clk);
+ struct regmap *regmap = pll->regmap;
+ const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+ u8 id = pll->id;
+ u32 mask = PLL_STATUS_MASK(id);
+ int offset = PLL_REG(id);
+ u8 out = 0;
+ unsigned int pllr;
+ unsigned int status;
+ u8 div;
+ u16 mul;
+
+ regmap_read(regmap, offset, &pllr);
+ div = PLL_DIV(pllr);
+ mul = PLL_MUL(pllr, layout);
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+ if ((status & mask) &&
+ (div == pll->div && mul == pll->mul))
+ return 0;
+
+ if (characteristics->out)
+ out = characteristics->out[pll->range];
+
+ if (characteristics->icpll)
+ regmap_write_bits(regmap, AT91_PMC_PLLICPR, PLL_ICPR_MASK(id),
+ characteristics->icpll[pll->range] << PLL_ICPR_SHIFT(id));
+
+ regmap_write_bits(regmap, offset, layout->pllr_mask,
+ pll->div | (PLL_MAX_COUNT << PLL_COUNT_SHIFT) |
+ (out << PLL_OUT_SHIFT) |
+ ((pll->mul & layout->mul_mask) << layout->mul_shift));
+
+ while (!clk_pll_ready(regmap, pll->id))
+ barrier();
+
+ return 0;
+}
+
+static int clk_pll_is_enabled(struct clk *clk)
+{
+ struct clk_pll *pll = to_clk_pll(clk);
+
+ return clk_pll_ready(pll->regmap, pll->id);
+}
+
+static void clk_pll_disable(struct clk *clk)
+{
+ struct clk_pll *pll = to_clk_pll(clk);
+ unsigned int mask = pll->layout->pllr_mask;
+
+ regmap_write_bits(pll->regmap, PLL_REG(pll->id), mask, ~mask);
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(clk);
+ unsigned int pllr;
+ u16 mul;
+ u8 div;
+
+ regmap_read(pll->regmap, PLL_REG(pll->id), &pllr);
+
+ div = PLL_DIV(pllr);
+ mul = PLL_MUL(pllr, pll->layout);
+
+ if (!div || !mul)
+ return 0;
+
+ return (parent_rate / div) * (mul + 1);
+}
+
+static long clk_pll_get_best_div_mul(struct clk_pll *pll, unsigned long rate,
+ unsigned long parent_rate,
+ u32 *div, u32 *mul,
+ u32 *index) {
+ const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+ unsigned long bestremainder = ULONG_MAX;
+ unsigned long maxdiv, mindiv, tmpdiv;
+ long bestrate = -ERANGE;
+ unsigned long bestdiv;
+ unsigned long bestmul;
+ int i = 0;
+
+ /* Check if parent_rate is a valid input rate */
+ if (parent_rate < characteristics->input.min)
+ return -ERANGE;
+
+ /*
+ * Calculate minimum divider based on the minimum multiplier, the
+ * parent_rate and the requested rate.
+ * Should always be 2 according to the input and output characteristics
+ * of the PLL blocks.
+ */
+ mindiv = (parent_rate * PLL_MUL_MIN) / rate;
+ if (!mindiv)
+ mindiv = 1;
+
+ if (parent_rate > characteristics->input.max) {
+ tmpdiv = DIV_ROUND_UP(parent_rate, characteristics->input.max);
+ if (tmpdiv > PLL_DIV_MAX)
+ return -ERANGE;
+
+ if (tmpdiv > mindiv)
+ mindiv = tmpdiv;
+ }
+
+ /*
+ * Calculate the maximum divider which is limited by PLL register
+ * layout (limited by the MUL or DIV field size).
+ */
+ maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX(layout), rate);
+ if (maxdiv > PLL_DIV_MAX)
+ maxdiv = PLL_DIV_MAX;
+
+ /*
+ * Iterate over the acceptable divider values to find the best
+ * divider/multiplier pair (the one that generates the closest
+ * rate to the requested one).
+ */
+ for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) {
+ unsigned long remainder;
+ unsigned long tmprate;
+ unsigned long tmpmul;
+
+ /*
+ * Calculate the multiplier associated with the current
+ * divider that provide the closest rate to the requested one.
+ */
+ tmpmul = DIV_ROUND_CLOSEST(rate, parent_rate / tmpdiv);
+ tmprate = (parent_rate / tmpdiv) * tmpmul;
+ if (tmprate > rate)
+ remainder = tmprate - rate;
+ else
+ remainder = rate - tmprate;
+
+ /*
+ * Compare the remainder with the best remainder found until
+ * now and elect a new best multiplier/divider pair if the
+ * current remainder is smaller than the best one.
+ */
+ if (remainder < bestremainder) {
+ bestremainder = remainder;
+ bestdiv = tmpdiv;
+ bestmul = tmpmul;
+ bestrate = tmprate;
+ }
+
+ /*
+ * We've found a perfect match!
+ * Stop searching now and use this multiplier/divider pair.
+ */
+ if (!remainder)
+ break;
+ }
+
+ /* We haven't found any multiplier/divider pair => return -ERANGE */
+ if (bestrate < 0)
+ return bestrate;
+
+ /* Check if bestrate is a valid output rate */
+ for (i = 0; i < characteristics->num_output; i++) {
+ if (bestrate >= characteristics->output[i].min &&
+ bestrate <= characteristics->output[i].max)
+ break;
+ }
+
+ if (i >= characteristics->num_output)
+ return -ERANGE;
+
+ if (div)
+ *div = bestdiv;
+ if (mul)
+ *mul = bestmul - 1;
+ if (index)
+ *index = i;
+
+ return bestrate;
+}
+
+static long clk_pll_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(clk);
+
+ return clk_pll_get_best_div_mul(pll, rate, *parent_rate,
+ NULL, NULL, NULL);
+}
+
+static int clk_pll_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(clk);
+ long ret;
+ u32 div;
+ u32 mul;
+ u32 index;
+
+ ret = clk_pll_get_best_div_mul(pll, rate, parent_rate,
+ &div, &mul, &index);
+ if (ret < 0)
+ return ret;
+
+ pll->range = index;
+ pll->div = div;
+ pll->mul = mul;
+
+ return 0;
+}
+
+static const struct clk_ops pll_ops = {
+ .enable = clk_pll_enable,
+ .disable = clk_pll_disable,
+ .is_enabled = clk_pll_is_enabled,
+ .recalc_rate = clk_pll_recalc_rate,
+ .round_rate = clk_pll_round_rate,
+ .set_rate = clk_pll_set_rate,
+};
+
+static struct clk *
+at91_clk_register_pll(struct regmap *regmap, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_layout *layout,
+ const struct clk_pll_characteristics *characteristics)
+{
+ struct clk_pll *pll;
+ int offset = PLL_REG(id);
+ unsigned int pllr;
+ int ret;
+
+ if (id > PLL_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ pll = xzalloc(sizeof(*pll));
+
+ pll->parent = parent_name;
+ pll->clk.name = name;
+ pll->clk.ops = &pll_ops;
+ pll->clk.parent_names = &pll->parent;
+ pll->clk.num_parents = 1;
+
+ /* init.flags = CLK_SET_RATE_GATE; */
+
+ pll->id = id;
+ pll->layout = layout;
+ pll->characteristics = characteristics;
+ pll->regmap = regmap;
+ regmap_read(regmap, offset, &pllr);
+ pll->div = PLL_DIV(pllr);
+ pll->mul = PLL_MUL(pllr, layout);
+
+ ret = clk_register(&pll->clk);
+ if (ret) {
+ kfree(pll);
+ return ERR_PTR(ret);
+ }
+
+ return &pll->clk;
+}
+
+
+static const struct clk_pll_layout at91rm9200_pll_layout = {
+ .pllr_mask = 0x7FFFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0x7FF,
+};
+
+static const struct clk_pll_layout at91sam9g45_pll_layout = {
+ .pllr_mask = 0xFFFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0xFF,
+};
+
+static const struct clk_pll_layout at91sam9g20_pllb_layout = {
+ .pllr_mask = 0x3FFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0x3F,
+};
+
+static const struct clk_pll_layout sama5d3_pll_layout = {
+ .pllr_mask = 0x1FFFFFF,
+ .mul_shift = 18,
+ .mul_mask = 0x7F,
+};
+
+
+static struct clk_pll_characteristics *
+of_at91_clk_pll_get_characteristics(struct device_node *np)
+{
+ int i;
+ int offset;
+ u32 tmp;
+ int num_output;
+ u32 num_cells;
+ struct clk_range input;
+ struct clk_range *output;
+ u8 *out = NULL;
+ u16 *icpll = NULL;
+ struct clk_pll_characteristics *characteristics;
+
+ if (of_at91_get_clk_range(np, "atmel,clk-input-range", &input))
+ return NULL;
+
+ if (of_property_read_u32(np, "#atmel,pll-clk-output-range-cells",
+ &num_cells))
+ return NULL;
+
+ if (num_cells < 2 || num_cells > 4)
+ return NULL;
+
+ if (!of_get_property(np, "atmel,pll-clk-output-ranges", &tmp))
+ return NULL;
+ num_output = tmp / (sizeof(u32) * num_cells);
+
+ characteristics = xzalloc(sizeof(*characteristics));
+ output = xzalloc(sizeof(*output) * num_output);
+
+ if (num_cells > 2)
+ out = xzalloc(sizeof(*out) * num_output);
+
+ if (num_cells > 3)
+ icpll = xzalloc(sizeof(*icpll) * num_output);
+
+
+ for (i = 0; i < num_output; i++) {
+ offset = i * num_cells;
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset, &tmp))
+ goto out_free_output;
+ output[i].min = tmp;
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 1, &tmp))
+ goto out_free_output;
+ output[i].max = tmp;
+
+ if (num_cells == 2)
+ continue;
+
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 2, &tmp))
+ goto out_free_output;
+ out[i] = tmp;
+
+ if (num_cells == 3)
+ continue;
+
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 3, &tmp))
+ goto out_free_output;
+ icpll[i] = tmp;
+ }
+
+ characteristics->input = input;
+ characteristics->num_output = num_output;
+ characteristics->output = output;
+ characteristics->out = out;
+ characteristics->icpll = icpll;
+ return characteristics;
+
+out_free_output:
+ kfree(icpll);
+ kfree(out);
+ kfree(output);
+ kfree(characteristics);
+ return NULL;
+}
+
+static int
+of_at91_clk_pll_setup(struct device_node *np,
+ const struct clk_pll_layout *layout)
+{
+ u32 id;
+ struct clk *clk;
+ struct regmap *regmap;
+ const char *parent_name;
+ const char *name = np->name;
+ struct clk_pll_characteristics *characteristics;
+
+ if (of_property_read_u32(np, "reg", &id))
+ return -EINVAL;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ characteristics = of_at91_clk_pll_get_characteristics(np);
+ if (!characteristics)
+ return -EINVAL;
+
+ clk = at91_clk_register_pll(regmap, name, parent_name, id, layout,
+ characteristics);
+ if (IS_ERR(clk)) {
+ kfree(characteristics);
+ return PTR_ERR(clk);
+ }
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+static int of_at91rm9200_clk_pll_setup(struct device_node *np)
+{
+ return of_at91_clk_pll_setup(np, &at91rm9200_pll_layout);
+}
+CLK_OF_DECLARE(at91rm9200_clk_pll, "atmel,at91rm9200-clk-pll",
+ of_at91rm9200_clk_pll_setup);
+
+static int of_at91sam9g45_clk_pll_setup(struct device_node *np)
+{
+ return of_at91_clk_pll_setup(np, &at91sam9g45_pll_layout);
+}
+CLK_OF_DECLARE(at91sam9g45_clk_pll, "atmel,at91sam9g45-clk-pll",
+ of_at91sam9g45_clk_pll_setup);
+
+static int of_at91sam9g20_clk_pllb_setup(struct device_node *np)
+{
+ return of_at91_clk_pll_setup(np, &at91sam9g20_pllb_layout);
+}
+CLK_OF_DECLARE(at91sam9g20_clk_pllb, "atmel,at91sam9g20-clk-pllb",
+ of_at91sam9g20_clk_pllb_setup);
+
+static int of_sama5d3_clk_pll_setup(struct device_node *np)
+{
+ return of_at91_clk_pll_setup(np, &sama5d3_pll_layout);
+}
+CLK_OF_DECLARE(sama5d3_clk_pll, "atmel,sama5d3-clk-pll",
+ of_sama5d3_clk_pll_setup);
diff --git a/drivers/clk/at91/clk-plldiv.c b/drivers/clk/at91/clk-plldiv.c
new file mode 100644
index 0000000000..917108e84c
--- /dev/null
+++ b/drivers/clk/at91/clk-plldiv.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define to_clk_plldiv(hw) container_of(clk, struct clk_plldiv, clk)
+
+struct clk_plldiv {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent;
+};
+
+static unsigned long clk_plldiv_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_plldiv *plldiv = to_clk_plldiv(clk);
+ unsigned int mckr;
+
+ regmap_read(plldiv->regmap, AT91_PMC_MCKR, &mckr);
+
+ if (mckr & AT91_PMC_PLLADIV2)
+ return parent_rate / 2;
+
+ return parent_rate;
+}
+
+static long clk_plldiv_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+
+ if (rate > *parent_rate)
+ return *parent_rate;
+ div = *parent_rate / 2;
+ if (rate < div)
+ return div;
+
+ if (rate - div < *parent_rate - rate)
+ return div;
+
+ return *parent_rate;
+}
+
+static int clk_plldiv_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_plldiv *plldiv = to_clk_plldiv(clk);
+
+ if ((parent_rate != rate) && (parent_rate / 2 != rate))
+ return -EINVAL;
+
+ regmap_write_bits(plldiv->regmap, AT91_PMC_MCKR, AT91_PMC_PLLADIV2,
+ parent_rate != rate ? AT91_PMC_PLLADIV2 : 0);
+
+ return 0;
+}
+
+static const struct clk_ops plldiv_ops = {
+ .recalc_rate = clk_plldiv_recalc_rate,
+ .round_rate = clk_plldiv_round_rate,
+ .set_rate = clk_plldiv_set_rate,
+};
+
+static struct clk *
+at91_clk_register_plldiv(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ int ret;
+ struct clk_plldiv *plldiv;
+
+ plldiv = xzalloc(sizeof(*plldiv));
+
+ plldiv->clk.name = name;
+ plldiv->clk.ops = &plldiv_ops;
+
+ if (parent_name) {
+ plldiv->parent = parent_name;
+ plldiv->clk.parent_names = &plldiv->parent;
+ plldiv->clk.num_parents = 1;
+ }
+
+ /* init.flags = CLK_SET_RATE_GATE; */
+
+ plldiv->regmap = regmap;
+
+ ret = clk_register(&plldiv->clk);
+ if (ret) {
+ kfree(plldiv);
+ return ERR_PTR(ret);
+ }
+
+ return &plldiv->clk;
+}
+
+static int
+of_at91sam9x5_clk_plldiv_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91_clk_register_plldiv(regmap, name, parent_name);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_plldiv, "atmel,at91sam9x5-clk-plldiv",
+ of_at91sam9x5_clk_plldiv_setup);
diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
new file mode 100644
index 0000000000..ddb18c0f7c
--- /dev/null
+++ b/drivers/clk/at91/clk-programmable.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <io.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define PROG_SOURCE_MAX 5
+#define PROG_ID_MAX 7
+
+#define PROG_STATUS_MASK(id) (1 << ((id) + 8))
+#define PROG_PRES_MASK 0x7
+#define PROG_PRES(layout, pckr) ((pckr >> layout->pres_shift) & PROG_PRES_MASK)
+#define PROG_MAX_RM9200_CSS 3
+
+struct clk_programmable_layout {
+ u8 pres_shift;
+ u8 css_mask;
+ u8 have_slck_mck;
+};
+
+struct clk_programmable {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 id;
+ const struct clk_programmable_layout *layout;
+ const char *parent_names[PROG_SOURCE_MAX];
+};
+
+#define to_clk_programmable(clk) container_of(clk, struct clk_programmable, clk)
+
+static unsigned long clk_programmable_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ unsigned int pckr;
+
+ regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr);
+
+ return parent_rate >> PROG_PRES(prog->layout, pckr);
+}
+
+static int clk_programmable_set_parent(struct clk *clk, u8 index)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned int mask = layout->css_mask;
+ unsigned int pckr = index;
+
+ if (layout->have_slck_mck)
+ mask |= AT91_PMC_CSSMCK_MCK;
+
+ if (index > layout->css_mask) {
+ if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck)
+ return -EINVAL;
+
+ pckr |= AT91_PMC_CSSMCK_MCK;
+ }
+
+ regmap_write_bits(prog->regmap, AT91_PMC_PCKR(prog->id), mask, pckr);
+
+ return 0;
+}
+
+static int clk_programmable_get_parent(struct clk *clk)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned int pckr;
+ u8 ret;
+
+ regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr);
+
+ ret = pckr & layout->css_mask;
+
+ if (layout->have_slck_mck && (pckr & AT91_PMC_CSSMCK_MCK) && !ret)
+ ret = PROG_MAX_RM9200_CSS + 1;
+
+ return ret;
+}
+
+static int clk_programmable_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned long div = parent_rate / rate;
+ unsigned int pckr;
+ int shift = 0;
+
+ regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr);
+
+ if (!div)
+ return -EINVAL;
+
+ shift = fls(div) - 1;
+
+ if (div != (1 << shift))
+ return -EINVAL;
+
+ if (shift >= PROG_PRES_MASK)
+ return -EINVAL;
+
+ regmap_write_bits(prog->regmap, AT91_PMC_PCKR(prog->id),
+ PROG_PRES_MASK << layout->pres_shift,
+ shift << layout->pres_shift);
+
+ return 0;
+}
+
+static const struct clk_ops programmable_ops = {
+ .recalc_rate = clk_programmable_recalc_rate,
+ .get_parent = clk_programmable_get_parent,
+ .set_parent = clk_programmable_set_parent,
+ .set_rate = clk_programmable_set_rate,
+};
+
+static struct clk *
+at91_clk_register_programmable(struct regmap *regmap,
+ const char *name, const char **parent_names,
+ u8 num_parents, u8 id,
+ const struct clk_programmable_layout *layout)
+{
+ struct clk_programmable *prog;
+ int ret;
+
+ if (id > PROG_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ prog = kzalloc(sizeof(*prog), GFP_KERNEL);
+ if (!prog)
+ return ERR_PTR(-ENOMEM);
+
+ prog->clk.name = name;
+ prog->clk.ops = &programmable_ops;
+ memcpy(prog->parent_names, parent_names,
+ num_parents * sizeof(prog->parent_names[0]));
+ prog->clk.parent_names = &prog->parent_names[0];
+ prog->clk.num_parents = num_parents;
+ /* init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; */
+
+ prog->id = id;
+ prog->layout = layout;
+ prog->regmap = regmap;
+
+ ret = clk_register(&prog->clk);
+ if (ret) {
+ kfree(prog);
+ return ERR_PTR(ret);
+ }
+
+ return &prog->clk;
+}
+
+static const struct clk_programmable_layout at91rm9200_programmable_layout = {
+ .pres_shift = 2,
+ .css_mask = 0x3,
+ .have_slck_mck = 0,
+};
+
+static const struct clk_programmable_layout at91sam9g45_programmable_layout = {
+ .pres_shift = 2,
+ .css_mask = 0x3,
+ .have_slck_mck = 1,
+};
+
+static const struct clk_programmable_layout at91sam9x5_programmable_layout = {
+ .pres_shift = 4,
+ .css_mask = 0x7,
+ .have_slck_mck = 0,
+};
+
+static int
+of_at91_clk_prog_setup(struct device_node *np,
+ const struct clk_programmable_layout *layout)
+{
+ int num;
+ u32 id;
+ struct clk *clk;
+ unsigned int num_parents;
+ const char *parent_names[PROG_SOURCE_MAX];
+ const char *name;
+ struct device_node *progclknp;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > PROG_SOURCE_MAX)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ num = of_get_child_count(np);
+ if (!num || num > (PROG_ID_MAX + 1))
+ return -EINVAL;
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ for_each_child_of_node(np, progclknp) {
+ if (of_property_read_u32(progclknp, "reg", &id))
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = progclknp->name;
+
+ clk = at91_clk_register_programmable(regmap, name,
+ parent_names, num_parents,
+ id, layout);
+ if (IS_ERR(clk))
+ continue;
+
+ of_clk_add_provider(progclknp, of_clk_src_simple_get, clk);
+ }
+
+ return 0;
+}
+
+
+static void __init of_at91rm9200_clk_prog_setup(struct device_node *np)
+{
+ of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout);
+}
+CLK_OF_DECLARE(at91rm9200_clk_prog, "atmel,at91rm9200-clk-programmable",
+ of_at91rm9200_clk_prog_setup);
+
+static int of_at91sam9g45_clk_prog_setup(struct device_node *np)
+{
+ return of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout);
+}
+CLK_OF_DECLARE(at91sam9g45_clk_prog, "atmel,at91sam9g45-clk-programmable",
+ of_at91sam9g45_clk_prog_setup);
+
+static int of_at91sam9x5_clk_prog_setup(struct device_node *np)
+{
+ return of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_prog, "atmel,at91sam9x5-clk-programmable",
+ of_at91sam9x5_clk_prog_setup);
diff --git a/drivers/clk/at91/clk-slow.c b/drivers/clk/at91/clk-slow.c
new file mode 100644
index 0000000000..d4981e7b4d
--- /dev/null
+++ b/drivers/clk/at91/clk-slow.c
@@ -0,0 +1,108 @@
+/*
+ * drivers/clk/at91/clk-slow.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <io.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+struct clk_sam9260_slow {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent_names[2];
+};
+
+#define to_clk_sam9260_slow(clk) container_of(clk, struct clk_sam9260_slow, clk)
+
+static int clk_sam9260_slow_get_parent(struct clk *clk)
+{
+ struct clk_sam9260_slow *slowck = to_clk_sam9260_slow(clk);
+ unsigned int status;
+
+ regmap_read(slowck->regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_OSCSEL ? 1 : 0;
+}
+
+static const struct clk_ops sam9260_slow_ops = {
+ .get_parent = clk_sam9260_slow_get_parent,
+};
+
+static struct clk * __init
+at91_clk_register_sam9260_slow(struct regmap *regmap,
+ const char *name,
+ const char **parent_names,
+ int num_parents)
+{
+ struct clk_sam9260_slow *slowck;
+ int ret;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ if (!parent_names || !num_parents)
+ return ERR_PTR(-EINVAL);
+
+ slowck = xzalloc(sizeof(*slowck));
+ slowck->clk.name = name;
+ slowck->clk.ops = &sam9260_slow_ops;
+ memcpy(slowck->parent_names, parent_names,
+ num_parents * sizeof(slowck->parent_names[0]));
+ slowck->clk.parent_names = slowck->parent_names;
+ slowck->clk.num_parents = num_parents;
+ slowck->regmap = regmap;
+
+ ret = clk_register(&slowck->clk);
+ if (ret) {
+ kfree(slowck);
+ return ERR_PTR(ret);
+ }
+
+ return &slowck->clk;
+}
+
+static int of_at91sam9260_clk_slow_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_names[2];
+ unsigned int num_parents;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents != 2)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91_clk_register_sam9260_slow(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+CLK_OF_DECLARE(at91sam9260_clk_slow, "atmel,at91sam9260-clk-slow",
+ of_at91sam9260_clk_slow_setup);
diff --git a/drivers/clk/at91/clk-smd.c b/drivers/clk/at91/clk-smd.c
new file mode 100644
index 0000000000..65c53efbba
--- /dev/null
+++ b/drivers/clk/at91/clk-smd.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <io.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define SMD_SOURCE_MAX 2
+
+#define SMD_DIV_SHIFT 8
+#define SMD_MAX_DIV 0xf
+
+struct at91sam9x5_clk_smd {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent_names[SMD_SOURCE_MAX];
+};
+
+#define to_at91sam9x5_clk_smd(clk) \
+ container_of(clk, struct at91sam9x5_clk_smd, clk)
+
+static unsigned long at91sam9x5_clk_smd_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk);
+ unsigned int smdr;
+ u8 smddiv;
+
+ regmap_read(smd->regmap, AT91_PMC_SMD, &smdr);
+ smddiv = (smdr & AT91_PMC_SMD_DIV) >> SMD_DIV_SHIFT;
+
+ return parent_rate / (smddiv + 1);
+}
+
+static long at91sam9x5_clk_smd_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+ unsigned long bestrate;
+ unsigned long tmp;
+
+ if (rate >= *parent_rate)
+ return *parent_rate;
+
+ div = *parent_rate / rate;
+ if (div > SMD_MAX_DIV)
+ return *parent_rate / (SMD_MAX_DIV + 1);
+
+ bestrate = *parent_rate / div;
+ tmp = *parent_rate / (div + 1);
+ if (bestrate - rate > rate - tmp)
+ bestrate = tmp;
+
+ return bestrate;
+}
+
+static int at91sam9x5_clk_smd_set_parent(struct clk *clk, u8 index)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk);
+
+ if (index > 1)
+ return -EINVAL;
+
+ regmap_write_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMDS,
+ index ? AT91_PMC_SMDS : 0);
+
+ return 0;
+}
+
+static int at91sam9x5_clk_smd_get_parent(struct clk *clk)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk);
+ unsigned int smdr;
+
+ regmap_read(smd->regmap, AT91_PMC_SMD, &smdr);
+
+ return smdr & AT91_PMC_SMDS;
+}
+
+static int at91sam9x5_clk_smd_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk);
+ unsigned long div = parent_rate / rate;
+
+ if (parent_rate % rate || div < 1 || div > (SMD_MAX_DIV + 1))
+ return -EINVAL;
+
+ regmap_write_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMD_DIV,
+ (div - 1) << SMD_DIV_SHIFT);
+
+ return 0;
+}
+
+static const struct clk_ops at91sam9x5_smd_ops = {
+ .recalc_rate = at91sam9x5_clk_smd_recalc_rate,
+ .round_rate = at91sam9x5_clk_smd_round_rate,
+ .get_parent = at91sam9x5_clk_smd_get_parent,
+ .set_parent = at91sam9x5_clk_smd_set_parent,
+ .set_rate = at91sam9x5_clk_smd_set_rate,
+};
+
+static struct clk *
+at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ struct at91sam9x5_clk_smd *smd;
+ int ret;
+
+ smd = xzalloc(sizeof(*smd));
+ smd->clk.name = name;
+ smd->clk.ops = &at91sam9x5_smd_ops;
+ memcpy(smd->parent_names, parent_names,
+ num_parents * sizeof(smd->parent_names[0]));
+ smd->clk.parent_names = smd->parent_names;
+ smd->clk.num_parents = num_parents;
+ /* init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; */
+ smd->regmap = regmap;
+
+ ret = clk_register(&smd->clk);
+ if (ret) {
+ kfree(smd);
+ return ERR_PTR(ret);
+ }
+
+ return &smd->clk;
+}
+
+static int of_at91sam9x5_clk_smd_setup(struct device_node *np)
+{
+ struct clk *clk;
+ unsigned int num_parents;
+ const char *parent_names[SMD_SOURCE_MAX];
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > SMD_SOURCE_MAX)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91sam9x5_clk_register_smd(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_smd, "atmel,at91sam9x5-clk-smd",
+ of_at91sam9x5_clk_smd_setup);
diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
new file mode 100644
index 0000000000..021930e546
--- /dev/null
+++ b/drivers/clk/at91/clk-system.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <io.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define SYSTEM_MAX_ID 31
+
+#define SYSTEM_MAX_NAME_SZ 32
+
+#define to_clk_system(clk) container_of(clk, struct clk_system, clk)
+struct clk_system {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 id;
+ const char *parent_name;
+};
+
+static inline int is_pck(int id)
+{
+ return (id >= 8) && (id <= 15);
+}
+
+static inline bool clk_system_ready(struct regmap *regmap, int id)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & (1 << id) ? 1 : 0;
+}
+
+static int clk_system_enable(struct clk *clk)
+{
+ struct clk_system *sys = to_clk_system(clk);
+
+ regmap_write(sys->regmap, AT91_PMC_SCER, 1 << sys->id);
+
+ if (!is_pck(sys->id))
+ return 0;
+
+ while (!clk_system_ready(sys->regmap, sys->id))
+ barrier();
+
+ return 0;
+}
+
+static void clk_system_disable(struct clk *clk)
+{
+ struct clk_system *sys = to_clk_system(clk);
+
+ regmap_write(sys->regmap, AT91_PMC_SCDR, 1 << sys->id);
+}
+
+static int clk_system_is_enabled(struct clk *clk)
+{
+ struct clk_system *sys = to_clk_system(clk);
+ unsigned int status;
+
+ regmap_read(sys->regmap, AT91_PMC_SCSR, &status);
+
+ if (!(status & (1 << sys->id)))
+ return 0;
+
+ if (!is_pck(sys->id))
+ return 1;
+
+ regmap_read(sys->regmap, AT91_PMC_SR, &status);
+
+ return status & (1 << sys->id) ? 1 : 0;
+}
+
+static const struct clk_ops system_ops = {
+ .enable = clk_system_enable,
+ .disable = clk_system_disable,
+ .is_enabled = clk_system_is_enabled,
+};
+
+static struct clk *
+at91_clk_register_system(struct regmap *regmap, const char *name,
+ const char *parent_name, u8 id)
+{
+ struct clk_system *sys;
+ int ret;
+
+ if (!parent_name || id > SYSTEM_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ sys = xzalloc(sizeof(*sys));
+ sys->clk.name = name;
+ sys->clk.ops = &system_ops;
+ sys->parent_name = parent_name;
+ sys->clk.parent_names = &sys->parent_name;
+ sys->clk.num_parents = 1;
+ /* init.flags = CLK_SET_RATE_PARENT; */
+ sys->id = id;
+ sys->regmap = regmap;
+
+ ret = clk_register(&sys->clk);
+ if (ret) {
+ kfree(sys);
+ return ERR_PTR(ret);
+ }
+
+ return &sys->clk;
+}
+
+static int of_at91rm9200_clk_sys_setup(struct device_node *np)
+{
+ int num;
+ u32 id;
+ struct clk *clk;
+ const char *name;
+ struct device_node *sysclknp;
+ const char *parent_name;
+ struct regmap *regmap;
+
+ num = of_get_child_count(np);
+ if (num > (SYSTEM_MAX_ID + 1))
+ return -EINVAL;
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ for_each_child_of_node(np, sysclknp) {
+ if (of_property_read_u32(sysclknp, "reg", &id))
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = sysclknp->name;
+
+ parent_name = of_clk_get_parent_name(sysclknp, 0);
+
+ clk = at91_clk_register_system(regmap, name, parent_name, id);
+ if (IS_ERR(clk))
+ continue;
+
+ of_clk_add_provider(sysclknp, of_clk_src_simple_get, clk);
+ }
+
+ return 0;
+}
+CLK_OF_DECLARE(at91rm9200_clk_sys, "atmel,at91rm9200-clk-system",
+ of_at91rm9200_clk_sys_setup);
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
new file mode 100644
index 0000000000..99ba671c98
--- /dev/null
+++ b/drivers/clk/at91/clk-usb.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <io.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define USB_SOURCE_MAX 2
+
+#define SAM9X5_USB_DIV_SHIFT 8
+#define SAM9X5_USB_MAX_DIV 0xf
+
+#define RM9200_USB_DIV_SHIFT 28
+#define RM9200_USB_DIV_TAB_SIZE 4
+
+struct at91sam9x5_clk_usb {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent_names[USB_SOURCE_MAX];
+};
+
+#define to_at91sam9x5_clk_usb(clk) \
+ container_of(clk, struct at91sam9x5_clk_usb, clk)
+
+struct at91rm9200_clk_usb {
+ struct clk clk;
+ struct regmap *regmap;
+ u32 divisors[4];
+ const char *parent_name;
+};
+
+#define to_at91rm9200_clk_usb(clk) \
+ container_of(clk, struct at91rm9200_clk_usb, clk)
+
+static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+ unsigned int usbr;
+ u8 usbdiv;
+
+ regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
+ usbdiv = (usbr & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT;
+
+ return DIV_ROUND_CLOSEST(parent_rate, (usbdiv + 1));
+}
+
+static int at91sam9x5_clk_usb_set_parent(struct clk *clk, u8 index)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+
+ if (index > 1)
+ return -EINVAL;
+
+ regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS,
+ index ? AT91_PMC_USBS : 0);
+
+ return 0;
+}
+
+static int at91sam9x5_clk_usb_get_parent(struct clk *clk)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+ unsigned int usbr;
+
+ regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
+
+ return usbr & AT91_PMC_USBS;
+}
+
+static int at91sam9x5_clk_usb_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+ unsigned long div;
+
+ if (!rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > SAM9X5_USB_MAX_DIV + 1 || !div)
+ return -EINVAL;
+
+ regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_OHCIUSBDIV,
+ (div - 1) << SAM9X5_USB_DIV_SHIFT);
+
+ return 0;
+}
+
+static const struct clk_ops at91sam9x5_usb_ops = {
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .get_parent = at91sam9x5_clk_usb_get_parent,
+ .set_parent = at91sam9x5_clk_usb_set_parent,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static int at91sam9n12_clk_usb_enable(struct clk *clk)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+
+ regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS,
+ AT91_PMC_USBS);
+
+ return 0;
+}
+
+static void at91sam9n12_clk_usb_disable(struct clk *clk)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+
+ regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS, 0);
+}
+
+static int at91sam9n12_clk_usb_is_enabled(struct clk *clk)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk);
+ unsigned int usbr;
+
+ regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
+
+ return usbr & AT91_PMC_USBS;
+}
+
+static const struct clk_ops at91sam9n12_usb_ops = {
+ .enable = at91sam9n12_clk_usb_enable,
+ .disable = at91sam9n12_clk_usb_disable,
+ .is_enabled = at91sam9n12_clk_usb_is_enabled,
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static struct clk *
+at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ struct at91sam9x5_clk_usb *usb;
+ int ret;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ usb->clk.name = name;
+ usb->clk.ops = &at91sam9x5_usb_ops;
+ memcpy(usb->parent_names, parent_names,
+ num_parents * sizeof(usb->parent_names[0]));
+ usb->clk.parent_names = usb->parent_names;
+ usb->clk.num_parents = num_parents;
+ usb->clk.flags = CLK_SET_RATE_PARENT;
+ /* init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE | */
+ /* CLK_SET_RATE_PARENT; */
+ usb->regmap = regmap;
+
+ ret = clk_register(&usb->clk);
+ if (ret) {
+ kfree(usb);
+ return ERR_PTR(ret);
+ }
+
+ return &usb->clk;
+}
+
+static struct clk *
+at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct at91sam9x5_clk_usb *usb;
+ int ret;
+
+ usb = xzalloc(sizeof(*usb));
+ usb->clk.name = name;
+ usb->clk.ops = &at91sam9n12_usb_ops;
+ usb->parent_names[0] = parent_name;
+ usb->clk.parent_names = &usb->parent_names[0];
+ usb->clk.num_parents = 1;
+ /* init.flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT; */
+ usb->regmap = regmap;
+
+ ret = clk_register(&usb->clk);
+ if (ret) {
+ kfree(usb);
+ return ERR_PTR(ret);
+ }
+
+ return &usb->clk;
+}
+
+static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(clk);
+ unsigned int pllbr;
+ u8 usbdiv;
+
+ regmap_read(usb->regmap, AT91_CKGR_PLLBR, &pllbr);
+
+ usbdiv = (pllbr & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT;
+ if (usb->divisors[usbdiv])
+ return parent_rate / usb->divisors[usbdiv];
+
+ return 0;
+}
+
+static long at91rm9200_clk_usb_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(clk);
+ struct clk *parent = clk_get_parent(clk);
+ unsigned long bestrate = 0;
+ int bestdiff = -1;
+ unsigned long tmprate;
+ int tmpdiff;
+ int i = 0;
+
+ for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
+ unsigned long tmp_parent_rate;
+
+ if (!usb->divisors[i])
+ continue;
+
+ tmp_parent_rate = rate * usb->divisors[i];
+ tmp_parent_rate = clk_round_rate(parent, tmp_parent_rate);
+ tmprate = DIV_ROUND_CLOSEST(tmp_parent_rate, usb->divisors[i]);
+ if (tmprate < rate)
+ tmpdiff = rate - tmprate;
+ else
+ tmpdiff = tmprate - rate;
+
+ if (bestdiff < 0 || bestdiff > tmpdiff) {
+ bestrate = tmprate;
+ bestdiff = tmpdiff;
+ *parent_rate = tmp_parent_rate;
+ }
+
+ if (!bestdiff)
+ break;
+ }
+
+ return bestrate;
+}
+
+static int at91rm9200_clk_usb_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ int i;
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(clk);
+ unsigned long div;
+
+ if (!rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+ for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
+ if (usb->divisors[i] == div) {
+ regmap_write_bits(usb->regmap, AT91_CKGR_PLLBR,
+ AT91_PMC_USBDIV,
+ i << RM9200_USB_DIV_SHIFT);
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct clk_ops at91rm9200_usb_ops = {
+ .recalc_rate = at91rm9200_clk_usb_recalc_rate,
+ .round_rate = at91rm9200_clk_usb_round_rate,
+ .set_rate = at91rm9200_clk_usb_set_rate,
+};
+
+static struct clk *
+at91rm9200_clk_register_usb(struct regmap *regmap, const char *name,
+ const char *parent_name, const u32 *divisors)
+{
+ struct at91rm9200_clk_usb *usb;
+ int ret;
+
+ usb = xzalloc(sizeof(*usb));
+ usb->clk.name = name;
+ usb->clk.ops = &at91rm9200_usb_ops;
+ usb->parent_name = parent_name;
+ usb->clk.parent_names = &usb->parent_name;
+ usb->clk.num_parents = 1;
+ /* init.flags = CLK_SET_RATE_PARENT; */
+
+ usb->regmap = regmap;
+ memcpy(usb->divisors, divisors, sizeof(usb->divisors));
+
+ ret = clk_register(&usb->clk);
+ if (ret) {
+ kfree(usb);
+ return ERR_PTR(ret);
+ }
+
+ return &usb->clk;
+}
+
+static int of_at91sam9x5_clk_usb_setup(struct device_node *np)
+{
+ struct clk *clk;
+ unsigned int num_parents;
+ const char *parent_names[USB_SOURCE_MAX];
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > USB_SOURCE_MAX)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91sam9x5_clk_register_usb(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_usb, "atmel,at91sam9x5-clk-usb",
+ of_at91sam9x5_clk_usb_setup);
+
+static int of_at91sam9n12_clk_usb_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return -EINVAL;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91sam9n12_clk_register_usb(regmap, name, parent_name);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91sam9n12_clk_usb, "atmel,at91sam9n12-clk-usb",
+ of_at91sam9n12_clk_usb_setup);
+
+static int of_at91rm9200_clk_usb_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ u32 divisors[4] = {0, 0, 0, 0};
+ struct regmap *regmap;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return -EINVAL;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4);
+ if (!divisors[0])
+ return -EINVAL;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ clk = at91rm9200_clk_register_usb(regmap, name, parent_name, divisors);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(at91rm9200_clk_usb, "atmel,at91rm9200-clk-usb",
+ of_at91rm9200_clk_usb_setup);
diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
new file mode 100644
index 0000000000..6a1c5e6df3
--- /dev/null
+++ b/drivers/clk/at91/clk-utmi.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define UTMI_FIXED_MUL 40
+
+struct clk_utmi {
+ struct clk clk;
+ struct regmap *regmap;
+ const char *parent;
+};
+
+#define to_clk_utmi(clk) container_of(clk, struct clk_utmi, clk)
+
+static inline bool clk_utmi_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_LOCKU;
+}
+
+static int clk_utmi_enable(struct clk *clk)
+{
+ struct clk_utmi *utmi = to_clk_utmi(clk);
+ unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT |
+ AT91_PMC_BIASEN;
+
+ regmap_write_bits(utmi->regmap, AT91_CKGR_UCKR, uckr, uckr);
+
+ while (!clk_utmi_ready(utmi->regmap))
+ barrier();
+
+ return 0;
+}
+
+static int clk_utmi_is_enabled(struct clk *clk)
+{
+ struct clk_utmi *utmi = to_clk_utmi(clk);
+
+ return clk_utmi_ready(utmi->regmap);
+}
+
+static void clk_utmi_disable(struct clk *clk)
+{
+ struct clk_utmi *utmi = to_clk_utmi(clk);
+
+ regmap_write_bits(utmi->regmap, AT91_CKGR_UCKR, AT91_PMC_UPLLEN, 0);
+}
+
+static unsigned long clk_utmi_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ /* UTMI clk is a fixed clk multiplier */
+ return parent_rate * UTMI_FIXED_MUL;
+}
+
+static const struct clk_ops utmi_ops = {
+ .enable = clk_utmi_enable,
+ .disable = clk_utmi_disable,
+ .is_enabled = clk_utmi_is_enabled,
+ .recalc_rate = clk_utmi_recalc_rate,
+};
+
+static struct clk * __init
+at91_clk_register_utmi(struct regmap *regmap,
+ const char *name, const char *parent_name)
+{
+ int ret;
+ struct clk_utmi *utmi;
+
+ utmi = xzalloc(sizeof(*utmi));
+
+ utmi->clk.name = name;
+ utmi->clk.ops = &utmi_ops;
+
+ if (parent_name) {
+ utmi->parent = parent_name;
+ utmi->clk.parent_names = &utmi->parent;
+ utmi->clk.num_parents = 1;
+ }
+
+ /* utmi->clk.flags = CLK_SET_RATE_GATE; */
+
+ utmi->regmap = regmap;
+
+ ret = clk_register(&utmi->clk);
+ if (ret) {
+ kfree(utmi);
+ return ERR_PTR(ret);
+ }
+
+ return &utmi->clk;
+}
+#if defined(CONFIG_OFTREE) && defined(CONFIG_COMMON_CLK_OF_PROVIDER)
+static void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ regmap = syscon_node_to_regmap(of_get_parent(np));
+ if (IS_ERR(regmap))
+ return;
+
+ clk = at91_clk_register_utmi(regmap, name, parent_name);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ return;
+}
+CLK_OF_DECLARE(at91sam9x5_clk_utmi, "atmel,at91sam9x5-clk-utmi",
+ of_at91sam9x5_clk_utmi_setup);
+#endif
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
new file mode 100644
index 0000000000..d156d50ca8
--- /dev/null
+++ b/drivers/clk/at91/pmc.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <module.h>
+#include <linux/list.h>
+#include <linux/clkdev.h>
+#include <of.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+int of_at91_get_clk_range(struct device_node *np, const char *propname,
+ struct clk_range *range)
+{
+ u32 min, max;
+ int ret;
+
+ ret = of_property_read_u32_index(np, propname, 0, &min);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u32_index(np, propname, 1, &max);
+ if (ret)
+ return ret;
+
+ if (range) {
+ range->min = min;
+ range->max = max;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_at91_get_clk_range);
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
new file mode 100644
index 0000000000..c6c14a79a4
--- /dev/null
+++ b/drivers/clk/at91/pmc.h
@@ -0,0 +1,27 @@
+/*
+ * drivers/clk/at91/pmc.h
+ *
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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.
+ */
+
+#ifndef __PMC_H_
+#define __PMC_H_
+
+#include <io.h>
+
+struct clk_range {
+ unsigned long min;
+ unsigned long max;
+};
+
+#define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,}
+
+int of_at91_get_clk_range(struct device_node *np, const char *propname,
+ struct clk_range *range);
+
+#endif /* __PMC_H_ */
diff --git a/drivers/clk/at91/sckc.c b/drivers/clk/at91/sckc.c
new file mode 100644
index 0000000000..bac28999ea
--- /dev/null
+++ b/drivers/clk/at91/sckc.c
@@ -0,0 +1,485 @@
+/*
+ * drivers/clk/at91/sckc.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ *
+ * 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 <common.h>
+#include <clock.h>
+#include <of.h>
+#include <of_address.h>
+#include <io.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+
+
+#define SLOW_CLOCK_FREQ 32768
+#define SLOWCK_SW_CYCLES 5
+#define SLOWCK_SW_TIME_USEC ((SLOWCK_SW_CYCLES * SECOND) / \
+ SLOW_CLOCK_FREQ)
+
+#define AT91_SCKC_CR 0x00
+#define AT91_SCKC_RCEN (1 << 0)
+#define AT91_SCKC_OSC32EN (1 << 1)
+#define AT91_SCKC_OSC32BYP (1 << 2)
+#define AT91_SCKC_OSCSEL (1 << 3)
+
+struct clk_slow_osc {
+ struct clk clk;
+ void __iomem *sckcr;
+ unsigned long startup_usec;
+ const char *parent_name;
+};
+
+#define to_clk_slow_osc(clk) container_of(clk, struct clk_slow_osc, clk)
+
+struct clk_sama5d4_slow_osc {
+ struct clk clk;
+ void __iomem *sckcr;
+ unsigned long startup_usec;
+ bool prepared;
+ const char *parent_name;
+};
+
+#define to_clk_sama5d4_slow_osc(clk) container_of(clk, struct clk_sama5d4_slow_osc, clk)
+
+struct clk_slow_rc_osc {
+ struct clk clk;
+ void __iomem *sckcr;
+ unsigned long frequency;
+ unsigned long startup_usec;
+ const char *parent_name;
+};
+
+#define to_clk_slow_rc_osc(clk) container_of(clk, struct clk_slow_rc_osc, clk)
+
+struct clk_sam9x5_slow {
+ struct clk clk;
+ void __iomem *sckcr;
+ u8 parent;
+ const char *parent_names[2];
+};
+
+#define to_clk_sam9x5_slow(clk) container_of(clk, struct clk_sam9x5_slow, clk)
+
+static int clk_slow_osc_enable(struct clk *clk)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(clk);
+ void __iomem *sckcr = osc->sckcr;
+ u32 tmp = readl(sckcr);
+
+ if (tmp & (AT91_SCKC_OSC32BYP | AT91_SCKC_OSC32EN))
+ return 0;
+
+ writel(tmp | AT91_SCKC_OSC32EN, sckcr);
+
+ udelay(osc->startup_usec);
+
+ return 0;
+}
+
+static void clk_slow_osc_disable(struct clk *clk)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(clk);
+ void __iomem *sckcr = osc->sckcr;
+ u32 tmp = readl(sckcr);
+
+ if (tmp & AT91_SCKC_OSC32BYP)
+ return;
+
+ writel(tmp & ~AT91_SCKC_OSC32EN, sckcr);
+}
+
+static int clk_slow_osc_is_enabled(struct clk *clk)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(clk);
+ void __iomem *sckcr = osc->sckcr;
+ u32 tmp = readl(sckcr);
+
+ if (tmp & AT91_SCKC_OSC32BYP)
+ return 1;
+
+ return !!(tmp & AT91_SCKC_OSC32EN);
+}
+
+static const struct clk_ops slow_osc_ops = {
+ .enable = clk_slow_osc_enable,
+ .disable = clk_slow_osc_disable,
+ .is_enabled = clk_slow_osc_is_enabled,
+};
+
+static struct clk *
+at91_clk_register_slow_osc(void __iomem *sckcr,
+ const char *name,
+ const char *parent_name,
+ unsigned long startup,
+ bool bypass)
+{
+ int ret;
+ struct clk_slow_osc *osc;
+
+ if (!sckcr || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ osc = xzalloc(sizeof(*osc));
+
+ osc->clk.name = name;
+ osc->clk.ops = &slow_osc_ops;
+ osc->parent_name = parent_name;
+ osc->clk.parent_names = &osc->parent_name;
+ osc->clk.num_parents = 1;
+
+ osc->sckcr = sckcr;
+ osc->startup_usec = startup;
+
+ if (bypass)
+ writel((readl(sckcr) & ~AT91_SCKC_OSC32EN) | AT91_SCKC_OSC32BYP,
+ sckcr);
+
+ ret = clk_register(&osc->clk);
+ if (ret) {
+ kfree(osc);
+ return ERR_PTR(ret);
+ }
+
+ return &osc->clk;
+}
+
+static void
+of_at91sam9x5_clk_slow_osc_setup(struct device_node *np, void __iomem *sckcr)
+{
+ struct clk *clk;
+ const char *parent_name;
+ const char *name = np->name;
+ u32 startup;
+ bool bypass;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ of_property_read_string(np, "clock-output-names", &name);
+ of_property_read_u32(np, "atmel,startup-time-usec", &startup);
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ clk = at91_clk_register_slow_osc(sckcr, name, parent_name, startup,
+ bypass);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+static unsigned long clk_slow_rc_osc_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk);
+
+ return osc->frequency;
+}
+
+static int clk_slow_rc_osc_enable(struct clk *clk)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk);
+ void __iomem *sckcr = osc->sckcr;
+
+ writel(readl(sckcr) | AT91_SCKC_RCEN, sckcr);
+
+ udelay(osc->startup_usec);
+
+ return 0;
+}
+
+static void clk_slow_rc_osc_disable(struct clk *clk)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk);
+ void __iomem *sckcr = osc->sckcr;
+
+ writel(readl(sckcr) & ~AT91_SCKC_RCEN, sckcr);
+}
+
+static int clk_slow_rc_osc_is_enabled(struct clk *clk)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk);
+
+ return !!(readl(osc->sckcr) & AT91_SCKC_RCEN);
+}
+
+static const struct clk_ops slow_rc_osc_ops = {
+ .enable = clk_slow_rc_osc_enable,
+ .disable = clk_slow_rc_osc_disable,
+ .is_enabled = clk_slow_rc_osc_is_enabled,
+ .recalc_rate = clk_slow_rc_osc_recalc_rate,
+};
+
+static struct clk *
+at91_clk_register_slow_rc_osc(void __iomem *sckcr,
+ const char *name,
+ unsigned long frequency,
+ unsigned long startup)
+{
+ struct clk_slow_rc_osc *osc;
+ int ret;
+
+ if (!sckcr || !name)
+ return ERR_PTR(-EINVAL);
+
+ osc = xzalloc(sizeof(*osc));
+ osc->clk.name = name;
+ osc->clk.ops = &slow_rc_osc_ops;
+ osc->clk.parent_names = NULL;
+ osc->clk.num_parents = 0;
+ /* init.flags = CLK_IGNORE_UNUSED; */
+
+ osc->sckcr = sckcr;
+ osc->frequency = frequency;
+ osc->startup_usec = startup;
+
+ ret = clk_register(&osc->clk);
+ if (ret) {
+ kfree(osc);
+ return ERR_PTR(ret);
+ }
+
+ return &osc->clk;
+}
+
+static void
+of_at91sam9x5_clk_slow_rc_osc_setup(struct device_node *np, void __iomem *sckcr)
+{
+ struct clk *clk;
+ u32 frequency = 0;
+ u32 startup = 0;
+ const char *name = np->name;
+
+ of_property_read_string(np, "clock-output-names", &name);
+ of_property_read_u32(np, "clock-frequency", &frequency);
+ of_property_read_u32(np, "atmel,startup-time-usec", &startup);
+
+ clk = at91_clk_register_slow_rc_osc(sckcr, name, frequency, startup);
+ if (IS_ERR(clk))
+ return;
+
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+static int clk_sam9x5_slow_set_parent(struct clk *clk, u8 index)
+{
+ struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(clk);
+ void __iomem *sckcr = slowck->sckcr;
+ u32 tmp;
+
+ if (index > 1)
+ return -EINVAL;
+
+ tmp = readl(sckcr);
+
+ if ((!index && !(tmp & AT91_SCKC_OSCSEL)) ||
+ (index && (tmp & AT91_SCKC_OSCSEL)))
+ return 0;
+
+ if (index)
+ tmp |= AT91_SCKC_OSCSEL;
+ else
+ tmp &= ~AT91_SCKC_OSCSEL;
+
+ writel(tmp, sckcr);
+
+ udelay(SLOWCK_SW_TIME_USEC);
+
+ return 0;
+}
+
+static int clk_sam9x5_slow_get_parent(struct clk *clk)
+{
+ struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(clk);
+
+ return !!(readl(slowck->sckcr) & AT91_SCKC_OSCSEL);
+}
+
+static const struct clk_ops sam9x5_slow_ops = {
+ .set_parent = clk_sam9x5_slow_set_parent,
+ .get_parent = clk_sam9x5_slow_get_parent,
+};
+
+static struct clk *
+at91_clk_register_sam9x5_slow(void __iomem *sckcr,
+ const char *name,
+ const char **parent_names,
+ int num_parents)
+{
+ struct clk_sam9x5_slow *slowck;
+ int ret;
+
+ if (!sckcr || !name || !parent_names || !num_parents)
+ return ERR_PTR(-EINVAL);
+
+ slowck = xzalloc(sizeof(*slowck));
+ slowck->clk.name = name;
+ slowck->clk.ops = &sam9x5_slow_ops;
+
+ memcpy(slowck->parent_names, parent_names,
+ num_parents * sizeof(slowck->parent_names[0]));
+ slowck->clk.parent_names = slowck->parent_names;
+ slowck->clk.num_parents = num_parents;
+ slowck->sckcr = sckcr;
+ slowck->parent = !!(readl(sckcr) & AT91_SCKC_OSCSEL);
+
+ ret = clk_register(&slowck->clk);
+ if (ret) {
+ kfree(slowck);
+ return ERR_PTR(ret);
+ }
+
+ return &slowck->clk;
+}
+
+static int
+of_at91sam9x5_clk_slow_setup(struct device_node *np, void __iomem *sckcr)
+{
+ struct clk *clk;
+ const char *parent_names[2];
+ unsigned int num_parents;
+ const char *name = np->name;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > 2)
+ return -EINVAL;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ clk = at91_clk_register_sam9x5_slow(sckcr, name, parent_names,
+ num_parents);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+static const struct of_device_id sckc_clk_ids[] = {
+ /* Slow clock */
+ {
+ .compatible = "atmel,at91sam9x5-clk-slow-osc",
+ .data = of_at91sam9x5_clk_slow_osc_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-slow-rc-osc",
+ .data = of_at91sam9x5_clk_slow_rc_osc_setup,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-slow",
+ .data = of_at91sam9x5_clk_slow_setup,
+ },
+ { /*sentinel*/ }
+};
+
+static int of_at91sam9x5_sckc_setup(struct device_node *np)
+{
+ struct device_node *childnp;
+ void (*clk_setup)(struct device_node *, void __iomem *);
+ const struct of_device_id *clk_id;
+ void __iomem *regbase = of_iomap(np, 0);
+
+ if (!regbase)
+ return -ENOMEM;
+
+ for_each_child_of_node(np, childnp) {
+ clk_id = of_match_node(sckc_clk_ids, childnp);
+ if (!clk_id)
+ continue;
+ clk_setup = clk_id->data;
+ clk_setup(childnp, regbase);
+ }
+
+ return 0;
+}
+CLK_OF_DECLARE(at91sam9x5_clk_sckc, "atmel,at91sam9x5-sckc",
+ of_at91sam9x5_sckc_setup);
+
+static int clk_sama5d4_slow_osc_enable(struct clk *clk)
+{
+ struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(clk);
+
+ if (osc->prepared)
+ return 0;
+
+ /*
+ * Assume that if it has already been selected (for example by the
+ * bootloader), enough time has aready passed.
+ */
+ if ((readl(osc->sckcr) & AT91_SCKC_OSCSEL)) {
+ osc->prepared = true;
+ return 0;
+ }
+
+ udelay(osc->startup_usec);
+ osc->prepared = true;
+
+ return 0;
+}
+
+static int clk_sama5d4_slow_osc_is_enabled(struct clk *clk)
+{
+ struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(clk);
+
+ return osc->prepared;
+}
+
+static const struct clk_ops sama5d4_slow_osc_ops = {
+ .enable = clk_sama5d4_slow_osc_enable,
+ .is_enabled = clk_sama5d4_slow_osc_is_enabled,
+};
+
+static int of_sama5d4_sckc_setup(struct device_node *np)
+{
+ void __iomem *regbase = of_iomap(np, 0);
+ struct clk *clk;
+ struct clk_sama5d4_slow_osc *osc;
+ const char *parent_names[2] = { "slow_rc_osc", "slow_osc" };
+ bool bypass;
+ int ret;
+
+ if (!regbase)
+ return -ENOMEM;
+
+ clk = clk_fixed(parent_names[0], 32768);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ osc = xzalloc(sizeof(*osc));
+ osc->parent_name = of_clk_get_parent_name(np, 0);
+ osc->clk.name = parent_names[1];
+ osc->clk.ops = &sama5d4_slow_osc_ops;
+ osc->clk.parent_names = &osc->parent_name;
+ osc->clk.num_parents = 1;
+ osc->sckcr = regbase;
+ osc->startup_usec = 1200000;
+
+ if (bypass)
+ writel((readl(regbase) | AT91_SCKC_OSC32BYP), regbase);
+
+ ret = clk_register(&osc->clk);
+ if (ret) {
+ kfree(osc);
+ return ret;
+ }
+
+ clk = at91_clk_register_sam9x5_slow(regbase, "slowck", parent_names, 2);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(sama5d4_clk_sckc, "atmel,sama5d4-sckc",
+ of_sama5d4_sckc_setup);
diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c
index a3dbf334a8..0be48558e6 100644
--- a/drivers/clk/clk-fixed-factor.c
+++ b/drivers/clk/clk-fixed-factor.c
@@ -93,7 +93,6 @@ struct clk *clk_fixed_factor(const char *name,
return &f->clk;
}
-#if defined(CONFIG_COMMON_CLK_OF_PROVIDER)
/**
* of_fixed_factor_clk_setup() - Setup function for simple fixed factor clock
*/
@@ -127,4 +126,3 @@ static int of_fixed_factor_clk_setup(struct device_node *node)
}
CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock",
of_fixed_factor_clk_setup);
-#endif
diff --git a/drivers/clk/clk-fixed.c b/drivers/clk/clk-fixed.c
index f0f7fbaed5..57bf36b39e 100644
--- a/drivers/clk/clk-fixed.c
+++ b/drivers/clk/clk-fixed.c
@@ -55,7 +55,6 @@ struct clk *clk_fixed(const char *name, int rate)
return &fix->clk;
}
-#if defined(CONFIG_COMMON_CLK_OF_PROVIDER)
/**
* of_fixed_clk_setup() - Setup function for simple fixed rate clock
*/
@@ -76,4 +75,3 @@ static int of_fixed_clk_setup(struct device_node *node)
return of_clk_add_provider(node, of_clk_src_simple_get, clk);
}
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
-#endif
diff --git a/drivers/clk/clk-gate-shared.c b/drivers/clk/clk-gate-shared.c
index a95f940dd2..c3b678a311 100644
--- a/drivers/clk/clk-gate-shared.c
+++ b/drivers/clk/clk-gate-shared.c
@@ -69,8 +69,8 @@ static struct clk_ops clk_gate_shared_ops = {
.is_enabled = clk_gate_shared_is_enabled,
};
-struct clk *clk_gate_shared_alloc(const char *name, const char *parent, const char *companion,
- unsigned flags)
+static struct clk *clk_gate_shared_alloc(const char *name, const char *parent,
+ const char *companion, unsigned flags)
{
struct clk_gate_shared *g = xzalloc(sizeof(*g));
@@ -86,7 +86,7 @@ struct clk *clk_gate_shared_alloc(const char *name, const char *parent, const ch
return &g->clk;
}
-void clk_gate_shared_free(struct clk *clk)
+static void clk_gate_shared_free(struct clk *clk)
{
struct clk_gate_shared *g = to_clk_gate_shared(clk);
diff --git a/drivers/clocksource/mvebu.c b/drivers/clocksource/mvebu.c
index cf80571263..59bbc4be22 100644
--- a/drivers/clocksource/mvebu.c
+++ b/drivers/clocksource/mvebu.c
@@ -60,7 +60,7 @@ static int mvebu_timer_probe(struct device_d *dev)
struct clk *clk;
u32 rate, div, val;
- iores = dev_request_mem_resource(dev, 0);
+ iores = dev_get_resource(dev, IORESOURCE_MEM, 0);
if (IS_ERR(iores))
return PTR_ERR(iores);
timer_base = IOMEM(iores->start);
diff --git a/drivers/clocksource/timer-atmel-pit.c b/drivers/clocksource/timer-atmel-pit.c
index cc7ad2f39a..947a1e7f49 100644
--- a/drivers/clocksource/timer-atmel-pit.c
+++ b/drivers/clocksource/timer-atmel-pit.c
@@ -102,9 +102,18 @@ static int at91_pit_probe(struct device_d *dev)
return init_clock(&cs);
}
+const static __maybe_unused struct of_device_id at91_pit_dt_ids[] = {
+ {
+ .compatible = "atmel,at91sam9260-pit",
+ }, {
+ /* sentinel */
+ }
+};
+
static struct driver_d at91_pit_driver = {
.name = "at91-pit",
.probe = at91_pit_probe,
+ .of_compatible = DRV_OF_COMPAT(at91_pit_dt_ids),
};
static int at91_pit_init(void)
diff --git a/drivers/crypto/caam/Kconfig b/drivers/crypto/caam/Kconfig
index cf05d1c077..2ab509d110 100644
--- a/drivers/crypto/caam/Kconfig
+++ b/drivers/crypto/caam/Kconfig
@@ -29,6 +29,7 @@ config CRYPTO_DEV_FSL_CAAM_RINGSIZE
config CRYPTO_DEV_FSL_CAAM_RNG
bool "Register caam RNG device"
depends on CRYPTO_DEV_FSL_CAAM
+ depends on HWRNG
default y
help
Selecting this will register the SEC4 hardware rng.
diff --git a/drivers/crypto/caam/caamrng.c b/drivers/crypto/caam/caamrng.c
index 0fef171a2b..31a92731d2 100644
--- a/drivers/crypto/caam/caamrng.c
+++ b/drivers/crypto/caam/caamrng.c
@@ -35,6 +35,7 @@
#include <driver.h>
#include <init.h>
#include <linux/spinlock.h>
+#include <linux/hw_random.h>
#include "regs.h"
#include "intern.h"
@@ -54,7 +55,7 @@
/* Buffer, its dma address and lock */
struct buf_data {
- u8 buf[RN_BUF_SIZE];
+ u8 *buf;
dma_addr_t addr;
u32 hw_desc[DESC_JOB_O_LEN];
#define BUF_NOT_EMPTY 0
@@ -71,7 +72,7 @@ struct caam_rng_ctx {
unsigned int cur_buf_idx;
int current_buf;
struct buf_data bufs[2];
- struct cdev cdev;
+ struct hwrng rng;
};
static struct caam_rng_ctx *rng_ctx;
@@ -116,8 +117,9 @@ static inline int submit_job(struct caam_rng_ctx *ctx, int to_current)
return err;
}
-static int caam_read(struct caam_rng_ctx *ctx, void *data, size_t max, bool wait)
+static int caam_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
+ struct caam_rng_ctx *ctx = container_of(rng, struct caam_rng_ctx, rng);
struct buf_data *bd = &ctx->bufs[ctx->current_buf];
int next_buf_idx, copied_idx;
int err;
@@ -162,7 +164,7 @@ static int caam_read(struct caam_rng_ctx *ctx, void *data, size_t max, bool wait
dev_dbg(ctx->jrdev, "switched to buffer %d\n", ctx->current_buf);
/* since there already is some data read, don't wait */
- return copied_idx + caam_read(ctx, data + copied_idx,
+ return copied_idx + caam_read(rng, data + copied_idx,
max - copied_idx, false);
}
@@ -216,6 +218,8 @@ static int caam_init_buf(struct caam_rng_ctx *ctx, int buf_id)
struct buf_data *bd = &ctx->bufs[buf_id];
int err;
+ bd->buf = dma_alloc(RN_BUF_SIZE);
+
err = rng_create_job_desc(ctx, buf_id);
if (err)
return err;
@@ -248,29 +252,6 @@ static int caam_init_rng(struct caam_rng_ctx *ctx, struct device_d *jrdev)
return 0;
}
-static ssize_t random_read(struct cdev *cdev, void *buf, size_t count,
- loff_t offset, ulong flags)
-{
- struct caam_rng_ctx *ctx = container_of(cdev, struct caam_rng_ctx, cdev);
-
- return caam_read(ctx, buf, count, true);
-}
-
-static struct file_operations randomops = {
- .read = random_read,
- .lseek = dev_lseek_default,
-};
-
-static int caam_init_devrandom(struct caam_rng_ctx *ctx, struct device_d *dev)
-{
- ctx->cdev.name = "hwrng";
- ctx->cdev.flags = DEVFS_IS_CHARACTER_DEV;
- ctx->cdev.ops = &randomops;
- ctx->cdev.dev = dev;
-
- return devfs_create(&ctx->cdev);
-}
-
int caam_rng_probe(struct device_d *dev, struct device_d *jrdev)
{
int err;
@@ -281,9 +262,14 @@ int caam_rng_probe(struct device_d *dev, struct device_d *jrdev)
if (err)
return err;
- err = caam_init_devrandom(rng_ctx, dev);
- if (err)
+ rng_ctx->rng.name = dev->name;
+ rng_ctx->rng.read = caam_read;
+
+ err = hwrng_register(dev, &rng_ctx->rng);
+ if (err) {
+ dev_err(dev, "rng-caam registering failed (%d)\n", err);
return err;
+ }
dev_info(dev, "registering rng-caam\n");
diff --git a/drivers/eeprom/at24.c b/drivers/eeprom/at24.c
index 4ae3776554..1227286fbe 100644
--- a/drivers/eeprom/at24.c
+++ b/drivers/eeprom/at24.c
@@ -500,6 +500,7 @@ static int at24_probe(struct device_d *dev)
goto err_devfs_create;
of_parse_partitions(&at24->cdev, dev->device_node);
+ of_partitions_register_fixup(&at24->cdev);
return 0;
diff --git a/drivers/eeprom/at25.c b/drivers/eeprom/at25.c
index f7f8368c4b..1caaebd371 100644
--- a/drivers/eeprom/at25.c
+++ b/drivers/eeprom/at25.c
@@ -360,6 +360,8 @@ static int at25_probe(struct device_d *dev)
dev_dbg(dev, "%s probed\n", at25->cdev.name);
of_parse_partitions(&at25->cdev, dev->device_node);
+ of_partitions_register_fixup(&at25->cdev);
+
return 0;
fail:
diff --git a/drivers/efi/efi-device.c b/drivers/efi/efi-device.c
index 6ed7f12b37..e9b03cb02a 100644
--- a/drivers/efi/efi-device.c
+++ b/drivers/efi/efi-device.c
@@ -101,7 +101,8 @@ static efi_handle_t *efi_find_parent(efi_handle_t *handle)
struct efi_open_protocol_information_entry *entry_buffer;
unsigned long entry_count;
- ret = efi_locate_handle(all_handles, NULL, NULL, &handle_count, &handles);
+ ret = efi_locate_handle(by_protocol, &efi_device_path_protocol_guid,
+ NULL, &handle_count, &handles);
if (ret)
return NULL;
@@ -183,8 +184,6 @@ static struct efi_device *efi_add_device(efi_handle_t *handle, efi_guid_t **guid
efidev->dev.info = efi_devinfo;
efidev->devpath = devpath;
- BS->handle_protocol(handle, &guidarr[0], &efidev->protocol);
-
sprintf(efidev->dev.name, "handle-%p", handle);
efidev->parent_handle = efi_find_parent(efidev->handle);
@@ -245,7 +244,8 @@ void efi_register_devices(void)
struct efi_device **efidevs;
int registered;
- ret = efi_locate_handle(all_handles, NULL, NULL, &handle_count, &handles);
+ ret = efi_locate_handle(by_protocol, &efi_device_path_protocol_guid,
+ NULL, &handle_count, &handles);
if (ret)
return;
@@ -310,8 +310,11 @@ static int efi_bus_match(struct device_d *dev, struct driver_d *drv)
int i;
for (i = 0; i < efidev->num_guids; i++) {
- if (!memcmp(&efidrv->guid, &efidev->guids[i], sizeof(efi_guid_t)))
+ if (!memcmp(&efidrv->guid, &efidev->guids[i], sizeof(efi_guid_t))) {
+ BS->handle_protocol(efidev->handle, &efidev->guids[i],
+ &efidev->protocol);
return 0;
+ }
}
return 1;
diff --git a/drivers/hab/Makefile b/drivers/hab/Makefile
index 8528ef954f..b169a1346d 100644
--- a/drivers/hab/Makefile
+++ b/drivers/hab/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_HABV4) += habv4.o
obj-$(CONFIG_HABV3) += habv3.o
+obj-$(CONFIG_HAB) += hab.o
diff --git a/drivers/hab/hab.c b/drivers/hab/hab.c
new file mode 100644
index 0000000000..512ff7ecf2
--- /dev/null
+++ b/drivers/hab/hab.c
@@ -0,0 +1,358 @@
+/*
+ * 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; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#define pr_fmt(fmt) "HAB: " fmt
+
+#include <common.h>
+#include <fcntl.h>
+#include <environment.h>
+#include <libfile.h>
+#include <mach/generic.h>
+#include <hab.h>
+#include <regmap.h>
+#include <fs.h>
+#include <mach/iim.h>
+#include <mach/imx25-fusemap.h>
+#include <mach/ocotp.h>
+#include <mach/imx6-fusemap.h>
+
+bool imx_hab_srk_hash_valid(const void *buf)
+{
+ const u8 *srk = buf;
+ int all_zero = 1, all_ff = 1;
+ int i;
+
+ for (i = 0; i < SRK_HASH_SIZE; i++) {
+ if (srk[i] != 0x0)
+ all_zero = 0;
+ if (srk[i] != 0xff)
+ all_ff = 0;
+ }
+
+ return !all_zero && !all_ff;
+}
+
+static int imx_hab_read_srk_hash_iim(u8 *srk)
+{
+ int ret, i;
+ unsigned int val;
+
+ ret = imx_iim_read_field(IMX25_IIM_SRK0_HASH_0, &val);
+ if (ret < 0)
+ return ret;
+ srk[0] = val;
+
+ for (i = 1; i < SRK_HASH_SIZE; i++) {
+ ret = imx_iim_read_field(IMX25_IIM_SRK0_HASH_1_31(i), &val);
+ if (ret < 0)
+ return ret;
+ srk[i] = val;
+ }
+
+ return 0;
+}
+
+static int imx_hab_write_srk_hash_iim(const u8 *srk, unsigned flags)
+{
+ int ret, i;
+
+ ret = imx_iim_write_field(IMX25_IIM_SRK0_HASH_0, srk[0]);
+ if (ret < 0)
+ return ret;
+
+ for (i = 1; i < SRK_HASH_SIZE; i++) {
+ ret = imx_iim_write_field(IMX25_IIM_SRK0_HASH_1_31(i), srk[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (flags & IMX_SRK_HASH_WRITE_PERMANENT) {
+ u8 verify[SRK_HASH_SIZE];
+
+ setenv("iim.explicit_sense_enable", "1");
+ ret = imx_hab_read_srk_hash_iim(verify);
+ if (ret)
+ return ret;
+ setenv("iim.explicit_sense_enable", "0");
+
+ if (memcmp(srk, verify, SRK_HASH_SIZE))
+ return -EIO;
+ }
+
+ if (flags & IMX_SRK_HASH_WRITE_LOCK) {
+ ret = imx_iim_write_field(IMX25_IIM_SRK0_LOCK96, 1);
+ if (ret < 0)
+ return ret;
+ ret = imx_iim_write_field(IMX25_IIM_SRK0_LOCK160, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_hab_permanent_write_enable_iim(int enable)
+{
+ return imx_iim_permanent_write(enable);
+}
+
+static int imx_hab_lockdown_device_iim(void)
+{
+ return imx_iim_write_field(IMX25_IIM_HAB_TYPE, 3);
+}
+
+static int imx_hab_device_locked_down_iim(void)
+{
+ int ret;
+ unsigned int v;
+
+ ret = imx_iim_read_field(IMX25_IIM_HAB_TYPE, &v);
+ if (ret < 0)
+ return ret;
+
+ return v == 1 ? false : true;
+}
+
+static int imx_hab_read_srk_hash_ocotp(u8 *__srk)
+{
+ u32 *srk = (u32 *)__srk;
+ int ret, i;
+
+ for (i = 0; i < SRK_HASH_SIZE / sizeof(uint32_t); i++) {
+ ret = imx_ocotp_read_field(OCOTP_SRK_HASH(i), &srk[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_hab_write_srk_hash_ocotp(const u8 *__newsrk, unsigned flags)
+{
+ u32 *newsrk = (u32 *)__newsrk;
+ int ret, i;
+
+ for (i = 0; i < SRK_HASH_SIZE / sizeof(uint32_t); i++) {
+ ret = imx_ocotp_write_field(OCOTP_SRK_HASH(i), newsrk[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (flags & IMX_SRK_HASH_WRITE_LOCK) {
+ ret = imx_ocotp_write_field(OCOTP_SRK_LOCK, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_hab_permanent_write_enable_ocotp(int enable)
+{
+ return imx_ocotp_permanent_write(enable);
+}
+
+static int imx_hab_lockdown_device_ocotp(void)
+{
+ return imx_ocotp_write_field(OCOTP_SEC_CONFIG_1, 1);
+}
+
+static int imx_hab_device_locked_down_ocotp(void)
+{
+ int ret;
+ unsigned int v;
+
+ ret = imx_ocotp_read_field(OCOTP_SEC_CONFIG_1, &v);
+ if (ret < 0)
+ return ret;
+
+ return v;
+}
+
+struct imx_hab_ops {
+ int (*init)(void);
+ int (*write_srk_hash)(const u8 *srk, unsigned flags);
+ int (*read_srk_hash)(u8 *srk);
+ int (*permanent_write_enable)(int enable);
+ int (*lockdown_device)(void);
+ int (*device_locked_down)(void);
+};
+
+static struct imx_hab_ops imx_hab_ops_iim = {
+ .write_srk_hash = imx_hab_write_srk_hash_iim,
+ .read_srk_hash = imx_hab_read_srk_hash_iim,
+ .lockdown_device = imx_hab_lockdown_device_iim,
+ .device_locked_down = imx_hab_device_locked_down_iim,
+ .permanent_write_enable = imx_hab_permanent_write_enable_iim,
+};
+
+static struct imx_hab_ops imx_hab_ops_ocotp = {
+ .write_srk_hash = imx_hab_write_srk_hash_ocotp,
+ .read_srk_hash = imx_hab_read_srk_hash_ocotp,
+ .lockdown_device = imx_hab_lockdown_device_ocotp,
+ .device_locked_down = imx_hab_device_locked_down_ocotp,
+ .permanent_write_enable = imx_hab_permanent_write_enable_ocotp,
+};
+
+static struct imx_hab_ops *imx_get_hab_ops(void)
+{
+ static struct imx_hab_ops *ops, *tmp;
+ int ret;
+
+ if (ops)
+ return ops;
+
+ if (cpu_is_mx25() || cpu_is_mx35())
+ tmp = &imx_hab_ops_iim;
+ else if (cpu_is_mx6())
+ tmp = &imx_hab_ops_ocotp;
+ else
+ return NULL;
+
+ if (tmp->init) {
+ ret = tmp->init();
+ if (ret)
+ return NULL;
+ }
+
+ ops = tmp;
+
+ return ops;
+}
+
+int imx_hab_read_srk_hash(void *buf)
+{
+ struct imx_hab_ops *ops = imx_get_hab_ops();
+
+ if (!ops)
+ return -ENOSYS;
+
+ return ops->read_srk_hash(buf);
+}
+
+int imx_hab_write_srk_hash(const void *buf, unsigned flags)
+{
+ u8 cursrk[SRK_HASH_SIZE];
+ int ret;
+ struct imx_hab_ops *ops = imx_get_hab_ops();
+
+ if (!ops)
+ return -ENOSYS;
+
+ ret = ops->read_srk_hash(cursrk);
+ if (ret) {
+ pr_err("Cannot read current SRK hash: %s\n", strerror(-ret));
+ return ret;
+ }
+
+ if (imx_hab_srk_hash_valid(cursrk)) {
+ char *str = "Current SRK hash is valid";
+
+ if (flags & IMX_SRK_HASH_FORCE) {
+ pr_warn("%s, ignoring\n", str);
+ } else {
+ pr_err("%s, refusing to burn again\n", str);
+ return -EEXIST;
+ }
+ }
+
+ if (flags & IMX_SRK_HASH_WRITE_PERMANENT) {
+ ret = ops->permanent_write_enable(1);
+ if (ret)
+ return ret;
+ }
+
+ ret = ops->write_srk_hash(buf, flags);
+
+ if (flags & IMX_SRK_HASH_WRITE_PERMANENT)
+ ops->permanent_write_enable(0);
+
+ return ret;
+}
+
+int imx_hab_write_srk_hash_file(const char *filename, unsigned flags)
+{
+ int ret;
+ size_t size;
+ void *srk;
+
+ ret = read_file_2(filename, &size, &srk, FILESIZE_MAX);
+ if (ret)
+ return ret;
+
+ if (size != SRK_HASH_SIZE) {
+ pr_err("File has wrong size, must be %d bytes\n", SRK_HASH_SIZE);
+ return -EINVAL;
+ }
+
+ ret = imx_hab_write_srk_hash(srk, flags);
+
+ free(srk);
+
+ return ret;
+}
+
+int imx_hab_write_srk_hash_hex(const char *srkhash, unsigned flags)
+{
+ int ret;
+ u8 srk[SRK_HASH_SIZE];
+
+ if (strlen(srkhash) != SRK_HASH_SIZE * 2) {
+ pr_err("Invalid srk hash %s\n", srkhash);
+ return -EINVAL;
+ }
+
+ ret = hex2bin(srk, srkhash, SRK_HASH_SIZE);
+ if (ret < 0) {
+ pr_err("Invalid srk hash %s\n", srkhash);
+ return -EINVAL;
+ }
+
+ return imx_hab_write_srk_hash(srk, flags);
+}
+
+int imx_hab_lockdown_device(unsigned flags)
+{
+ struct imx_hab_ops *ops = imx_get_hab_ops();
+ u8 srk[SRK_HASH_SIZE];
+ int ret;
+
+ ret = imx_hab_read_srk_hash(srk);
+ if (ret)
+ return ret;
+
+ if (!imx_hab_srk_hash_valid(srk)) {
+ pr_err("No SRK hash burnt into fuses. Refusing to lock device\n");
+ return -EINVAL;
+ }
+
+ if (!ops)
+ return -ENOSYS;
+
+ if (flags & IMX_SRK_HASH_WRITE_PERMANENT) {
+ ret = ops->permanent_write_enable(1);
+ if (ret)
+ return ret;
+ }
+
+ ret = ops->lockdown_device();
+
+ if (flags & IMX_SRK_HASH_WRITE_PERMANENT)
+ ops->permanent_write_enable(0);
+
+ return ret;
+}
+
+int imx_hab_device_locked_down(void)
+{
+ struct imx_hab_ops *ops = imx_get_hab_ops();
+
+ return ops->device_locked_down();
+}
diff --git a/drivers/hw_random/Kconfig b/drivers/hw_random/Kconfig
new file mode 100644
index 0000000000..807fcadd31
--- /dev/null
+++ b/drivers/hw_random/Kconfig
@@ -0,0 +1,6 @@
+menuconfig HWRNG
+ bool "HWRNG Support"
+ help
+ Support for HWRNG(Hardware Random Number Generator) devices.
+
+ If unsure, say no.
diff --git a/drivers/hw_random/Makefile b/drivers/hw_random/Makefile
new file mode 100644
index 0000000000..15307b100f
--- /dev/null
+++ b/drivers/hw_random/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_HWRNG) += core.o
diff --git a/drivers/hw_random/core.c b/drivers/hw_random/core.c
new file mode 100644
index 0000000000..ef2a988c76
--- /dev/null
+++ b/drivers/hw_random/core.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2016 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * derived from Linux kernel drivers/char/hw_random/core.c
+ */
+
+#include <common.h>
+#include <linux/hw_random.h>
+#include <malloc.h>
+
+static LIST_HEAD(hwrngs);
+
+#define RNG_BUFFER_SIZE 32
+
+int hwrng_get_data(struct hwrng *rng, void *buffer, size_t size, int wait)
+{
+ return rng->read(rng, buffer, size, wait);
+}
+
+static int hwrng_init(struct hwrng *rng)
+{
+ int ret = 0;
+
+ if (rng->init)
+ ret = rng->init(rng);
+
+ if (!ret)
+ list_add_tail(&rng->list, &hwrngs);
+
+ return ret;
+}
+
+static ssize_t rng_dev_read(struct cdev *cdev, void *buf, size_t size,
+ loff_t offset, unsigned long flags)
+{
+ struct hwrng *rng = container_of(cdev, struct hwrng, cdev);
+ size_t count = size;
+ ssize_t cur = 0;
+ int len;
+
+ memset(buf, 0, size);
+
+ while (count) {
+ int max = min(count, (size_t)RNG_BUFFER_SIZE);
+ len = hwrng_get_data(rng, rng->buf, max, true);
+ if (len < 0) {
+ cur = len;
+ break;
+ }
+
+ memcpy(buf + cur, rng->buf, len);
+
+ count -= len;
+ cur += len;
+ }
+
+ return cur;
+}
+
+static struct file_operations rng_chrdev_ops = {
+ .read = rng_dev_read,
+ .lseek = dev_lseek_default,
+};
+
+static int hwrng_register_cdev(struct hwrng *rng)
+{
+ struct device_d *dev = rng->dev;
+ const char *alias;
+ char *devname;
+ int err;
+
+ alias = of_alias_get(dev->device_node);
+ if (alias) {
+ devname = xstrdup(alias);
+ } else {
+ err = cdev_find_free_index("hwrng");
+ if (err < 0) {
+ dev_err(dev, "no index found to name device\n");
+ return err;
+ }
+ devname = xasprintf("hwrng%d", err);
+ }
+
+ rng->cdev.name = devname;
+ rng->cdev.flags = DEVFS_IS_CHARACTER_DEV;
+ rng->cdev.ops = &rng_chrdev_ops;
+ rng->cdev.dev = rng->dev;
+
+ return devfs_create(&rng->cdev);
+}
+
+struct hwrng *hwrng_get_first(void)
+{
+ if (list_empty(&hwrngs))
+ return ERR_PTR(-ENODEV);
+ else
+ return list_first_entry(&hwrngs, struct hwrng, list);
+}
+
+int hwrng_register(struct device_d *dev, struct hwrng *rng)
+{
+ int err;
+
+ if (rng->name == NULL || rng->read == NULL)
+ return -EINVAL;
+
+ rng->buf = xzalloc(RNG_BUFFER_SIZE);
+ rng->dev = dev;
+
+ err = hwrng_init(rng);
+ if (err) {
+ free(rng->buf);
+ return err;
+ }
+
+ err = hwrng_register_cdev(rng);
+ if (err)
+ free(rng->buf);
+
+ return err;
+}
diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig
index 155a78a7df..50f0d8f974 100644
--- a/drivers/led/Kconfig
+++ b/drivers/led/Kconfig
@@ -1,5 +1,6 @@
menuconfig LED
bool "LED support"
+ select POLLER
if LED
diff --git a/drivers/led/core.c b/drivers/led/core.c
index 30b016bb34..6f66de0fbb 100644
--- a/drivers/led/core.c
+++ b/drivers/led/core.c
@@ -23,6 +23,7 @@
#include <linux/list.h>
#include <errno.h>
#include <led.h>
+#include <init.h>
#include <poller.h>
#include <clock.h>
#include <linux/ctype.h>
@@ -101,7 +102,7 @@ struct led *led_by_name_or_number(const char *str)
* @param led the led
* @param value the value of the LED (0 is disabled)
*/
-int led_set(struct led *led, unsigned int value)
+static int __led_set(struct led *led, unsigned int value)
{
if (value > led->max_value)
value = led->max_value;
@@ -114,6 +115,104 @@ int led_set(struct led *led, unsigned int value)
return 0;
}
+int led_set(struct led *led, unsigned int value)
+{
+ led->blink = 0;
+ led->flash = 0;
+ return __led_set(led, value);
+}
+
+static void led_blink_func(struct poller_struct *poller)
+{
+ struct led *led;
+
+ list_for_each_entry(led, &leds, list) {
+ bool on;
+
+ if (!led->blink && !led->flash)
+ continue;
+
+ if (led->blink_next_event > get_time_ns()) {
+ continue;
+ }
+
+ on = !(led->blink_next_state % 2);
+
+ led->blink_next_event = get_time_ns() +
+ (led->blink_states[led->blink_next_state] * MSECOND);
+ led->blink_next_state = (led->blink_next_state + 1) %
+ led->blink_nr_states;
+
+ if (led->flash && !on)
+ led->flash = 0;
+
+ __led_set(led, on);
+ }
+}
+
+/**
+ * led_blink_pattern - Blink a led with flexible timings.
+ * @led LED used
+ * @pattern Array of millisecond intervals describing the on and off periods of
+ * the pattern. At the end of the array/pattern it is repeated. The array
+ * starts with an on-period. In general every array item with even index
+ * describes an on-period, every item with odd index an off-period.
+ * @pattern_len Length of the pattern array.
+ *
+ * Returns 0 on success.
+ *
+ * Example:
+ * pattern = {500, 1000};
+ * This will enable the LED for 500ms and disable it for 1000ms after
+ * that. This is repeated forever.
+ */
+int led_blink_pattern(struct led *led, const unsigned int *pattern,
+ unsigned int pattern_len)
+{
+ free(led->blink_states);
+ led->blink_states = xmemdup(pattern,
+ pattern_len * sizeof(*led->blink_states));
+ led->blink_nr_states = pattern_len;
+ led->blink_next_state = 0;
+ led->blink_next_event = get_time_ns();
+ led->blink = 1;
+ led->flash = 0;
+
+ return 0;
+}
+
+int led_blink(struct led *led, unsigned int on_ms, unsigned int off_ms)
+{
+ unsigned int pattern[] = {on_ms, off_ms};
+
+ return led_blink_pattern(led, pattern, 2);
+}
+
+int led_flash(struct led *led, unsigned int duration_ms)
+{
+ unsigned int pattern[] = {duration_ms, 0};
+ int ret;
+
+ ret = led_blink_pattern(led, pattern, 2);
+ if (ret)
+ return ret;
+
+ led->flash = 1;
+ led->blink = 0;
+
+ return 0;
+}
+
+static struct poller_struct led_poller = {
+ .func = led_blink_func,
+};
+
+static int led_blink_init(void)
+{
+ return poller_register(&led_poller);
+}
+late_initcall(led_blink_init);
+
/**
* led_set_num - set the value of a LED
* @param num the number of the LED
diff --git a/drivers/led/led-triggers.c b/drivers/led/led-triggers.c
index dee936739a..76a1481e14 100644
--- a/drivers/led/led-triggers.c
+++ b/drivers/led/led-triggers.c
@@ -48,33 +48,11 @@
struct led_trigger_struct {
struct led *led;
- uint64_t flash_start;
- int flash;
+ struct list_head list;
+ enum led_trigger trigger;
};
-static struct led_trigger_struct triggers[LED_TRIGGER_MAX];
-
-static void trigger_func(struct poller_struct *poller)
-{
- int i;
-
- for (i = 0; i < LED_TRIGGER_MAX; i++) {
- if (triggers[i].led &&
- triggers[i].flash &&
- is_timeout(triggers[i].flash_start, 200 * MSECOND)) {
- led_set(triggers[i].led, 0);
- triggers[i].flash = 0;
- }
- }
-
- if (triggers[LED_TRIGGER_HEARTBEAT].led &&
- is_timeout(triggers[LED_TRIGGER_HEARTBEAT].flash_start, SECOND))
- led_trigger(LED_TRIGGER_HEARTBEAT, TRIGGER_FLASH);
-}
-
-static struct poller_struct trigger_poller = {
- .func = trigger_func,
-};
+static LIST_HEAD(led_triggers);
/**
* led_trigger - triggers a trigger
@@ -85,21 +63,53 @@ static struct poller_struct trigger_poller = {
*/
void led_trigger(enum led_trigger trigger, enum trigger_type type)
{
+ struct led_trigger_struct *led_trigger;
+
if (trigger >= LED_TRIGGER_MAX)
return;
- if (!triggers[trigger].led)
- return;
- if (type == TRIGGER_FLASH) {
- if (is_timeout(triggers[trigger].flash_start, 400 * MSECOND)) {
- led_set(triggers[trigger].led, triggers[trigger].led->max_value);
- triggers[trigger].flash_start = get_time_ns();
- triggers[trigger].flash = 1;
+ list_for_each_entry(led_trigger, &led_triggers, list) {
+ if (led_trigger->trigger != trigger)
+ continue;
+
+ switch (type) {
+ case TRIGGER_FLASH:
+ led_flash(led_trigger->led, 200);
+ break;
+ case TRIGGER_ENABLE:
+ led_set(led_trigger->led, led_trigger->led->max_value);
+ break;
+ case TRIGGER_DISABLE:
+ led_set(led_trigger->led, 0);
+ break;
}
- return;
}
+}
+
+static struct led_trigger_struct *led_find_trigger(struct led *led)
+{
+ struct led_trigger_struct *led_trigger;
+
+ list_for_each_entry(led_trigger, &led_triggers, list)
+ if (led_trigger->led == led)
+ return led_trigger;
+
+ return NULL;
+}
+
+void led_trigger_disable(struct led *led)
+{
+ struct led_trigger_struct *led_trigger;
+
+ led_trigger = led_find_trigger(led);
+ if (!led_trigger)
+ return;
+
+ list_del(&led_trigger->list);
+
+ led_set(led, 0);
- led_set(triggers[trigger].led, type == TRIGGER_ENABLE ? triggers[trigger].led->max_value : 0);
+ free(led_trigger);
}
/**
@@ -112,44 +122,73 @@ void led_trigger(enum led_trigger trigger, enum trigger_type type)
*/
int led_set_trigger(enum led_trigger trigger, struct led *led)
{
- int i;
+ struct led_trigger_struct *led_trigger;
if (trigger >= LED_TRIGGER_MAX)
return -EINVAL;
- if (led)
- for (i = 0; i < LED_TRIGGER_MAX; i++)
- if (triggers[i].led == led)
- return -EBUSY;
+ led_trigger_disable(led);
- if (triggers[trigger].led && !led)
- led_set(triggers[trigger].led, 0);
+ led_trigger = xzalloc(sizeof(*led_trigger));
- triggers[trigger].led = led;
+ led_trigger->led = led;
+ led_trigger->trigger = trigger;
+ list_add_tail(&led_trigger->list, &led_triggers);
- if (led && trigger == LED_TRIGGER_DEFAULT_ON)
- led_set(triggers[trigger].led, triggers[trigger].led->max_value);
+ if (trigger == LED_TRIGGER_DEFAULT_ON)
+ led_set(led, led->max_value);
+ if (trigger == LED_TRIGGER_HEARTBEAT)
+ led_blink(led, 200, 1000);
return 0;
}
-/**
- * led_get_trigger - get the LED for a trigger
- * @param trigger The trigger to set a LED for
- *
- * return the LED number of a trigger.
- */
-int led_get_trigger(enum led_trigger trigger)
+static char *trigger_names[] = {
+ [LED_TRIGGER_PANIC] = "panic",
+ [LED_TRIGGER_HEARTBEAT] = "heartbeat",
+ [LED_TRIGGER_NET_RX] = "net-rx",
+ [LED_TRIGGER_NET_TX] = "net-tx",
+ [LED_TRIGGER_NET_TXRX] = "net",
+ [LED_TRIGGER_DEFAULT_ON] = "default-on",
+};
+
+const char *trigger_name(enum led_trigger trigger)
{
- if (trigger >= LED_TRIGGER_MAX)
- return -EINVAL;
- if (!triggers[trigger].led)
- return -ENODEV;
- return led_get_number(triggers[trigger].led);
+ return trigger_names[trigger];
+}
+
+enum led_trigger trigger_by_name(const char *name)
+{
+ int i;
+
+ for (i = 0; i < LED_TRIGGER_MAX; i++)
+ if (!strcmp(name, trigger_names[i]))
+ return i;
+
+ return LED_TRIGGER_MAX;
}
-static int trigger_init(void)
+/**
+ * led_triggers_show_info - Show information about all registered
+ * triggers
+ */
+void led_triggers_show_info(void)
{
- return poller_register(&trigger_poller);
+ struct led_trigger_struct *led_trigger;
+ int i;
+
+ for (i = 0; i < LED_TRIGGER_MAX; i++) {
+ printf("%s", trigger_name(i));
+
+ list_for_each_entry(led_trigger, &led_triggers, list) {
+ struct led *led = led_trigger->led;
+
+ if (led_trigger->trigger != i)
+ continue;
+
+ printf("\n LED %d (%s)", led->num, led->name);
+ }
+
+ printf("\n");
+ }
}
-late_initcall(trigger_init);
diff --git a/drivers/mci/atmel_mci.c b/drivers/mci/atmel_mci.c
index 2a0ddb052b..317cf46022 100644
--- a/drivers/mci/atmel_mci.c
+++ b/drivers/mci/atmel_mci.c
@@ -24,6 +24,7 @@
#include <mach/board.h>
#include <linux/clk.h>
#include <linux/err.h>
+#include <of_gpio.h>
#include "atmel-mci-regs.h"
@@ -53,6 +54,7 @@ struct atmel_mci {
u32 cfg_reg;
u32 sdc_reg;
bool need_reset;
+ int detect_pin;
};
#define to_mci_host(mci) container_of(mci, struct atmel_mci, mci)
@@ -360,14 +362,13 @@ static int atmci_start_cmd(struct atmel_mci *host, struct mci_cmd *cmd,
static int atmci_card_present(struct mci_host *mci)
{
struct atmel_mci *host = to_mci_host(mci);
- struct atmel_mci_platform_data *pd = host->hw_dev->platform_data;
int ret;
/* No gpio, assume card is present */
- if (!gpio_is_valid(pd->detect_pin))
+ if (!gpio_is_valid(host->detect_pin))
return 1;
- ret = gpio_get_value(pd->detect_pin);
+ ret = gpio_get_value(host->detect_pin);
return ret == 0 ? 1 : 0;
}
@@ -535,44 +536,71 @@ static int atmci_probe(struct device_d *hw_dev)
{
struct resource *iores;
struct atmel_mci *host;
+ struct device_node *np = hw_dev->device_node;
struct atmel_mci_platform_data *pd = hw_dev->platform_data;
int ret;
- if (!pd) {
- dev_err(hw_dev, "missing platform data\n");
- return -EINVAL;
+ host = xzalloc(sizeof(*host));
+ host->mci.send_cmd = atmci_request;
+ host->mci.set_ios = atmci_set_ios;
+ host->mci.init = atmci_reset;
+ host->mci.card_present = atmci_card_present;
+ host->mci.hw_dev = hw_dev;
+ host->detect_pin = -EINVAL;
+
+ if (pd) {
+ host->detect_pin = pd->detect_pin;
+ host->mci.devname = pd->devname;
+
+ if (pd->bus_width >= 4)
+ host->mci.host_caps |= MMC_CAP_4_BIT_DATA;
+ if (pd->bus_width == 8)
+ host->mci.host_caps |= MMC_CAP_8_BIT_DATA;
+
+ host->slot_b = pd->slot_b;
+ } else if (np) {
+ u32 slot_id;
+ struct device_node *cnp;
+ const char *alias = of_alias_get(np);
+
+ if (alias)
+ host->mci.devname = xstrdup(alias);
+
+ host->detect_pin = of_get_named_gpio(np, "cd-gpios", 0);
+
+ for_each_child_of_node(np, cnp) {
+ if (of_property_read_u32(cnp, "reg", &slot_id)) {
+ dev_warn(hw_dev, "reg property is missing for %s\n",
+ cnp->full_name);
+ continue;
+ }
+
+ host->slot_b = slot_id;
+ mci_of_parse_node(&host->mci, cnp);
+ break;
+ }
+ } else {
+ dev_err(hw_dev, "Missing device information\n");
+ ret = -EINVAL;
+ goto error_out;
}
- if (gpio_is_valid(pd->detect_pin)) {
- ret = gpio_request(pd->detect_pin, "mci_cd");
+ if (gpio_is_valid(host->detect_pin)) {
+ ret = gpio_request(host->detect_pin, "mci_cd");
if (ret) {
dev_err(hw_dev, "Impossible to request CD gpio %d (%d)\n",
- ret, pd->detect_pin);
- return ret;
+ ret, host->detect_pin);
+ goto error_out;
}
- ret = gpio_direction_input(pd->detect_pin);
+ ret = gpio_direction_input(host->detect_pin);
if (ret) {
dev_err(hw_dev, "Impossible to configure CD gpio %d as input (%d)\n",
- ret, pd->detect_pin);
- goto err_gpio_cd_request;
+ ret, host->detect_pin);
+ goto error_out;
}
}
- host = xzalloc(sizeof(*host));
- host->mci.send_cmd = atmci_request;
- host->mci.set_ios = atmci_set_ios;
- host->mci.init = atmci_reset;
- host->mci.card_present = atmci_card_present;
- host->mci.hw_dev = hw_dev;
- host->mci.devname = pd->devname;
-
- if (pd->bus_width >= 4)
- host->mci.host_caps |= MMC_CAP_4_BIT_DATA;
- if (pd->bus_width == 8)
- host->mci.host_caps |= MMC_CAP_8_BIT_DATA;
- host->slot_b = pd->slot_b;
-
iores = dev_request_mem_resource(hw_dev, 0);
if (IS_ERR(iores))
return PTR_ERR(iores);
@@ -583,7 +611,7 @@ static int atmci_probe(struct device_d *hw_dev)
if (IS_ERR(host->clk)) {
dev_err(hw_dev, "no mci_clk\n");
ret = PTR_ERR(host->clk);
- goto err_gpio_cd_request;
+ goto error_out;
}
clk_enable(host->clk);
@@ -614,15 +642,26 @@ static int atmci_probe(struct device_d *hw_dev)
return 0;
-err_gpio_cd_request:
- if (gpio_is_valid(pd->detect_pin))
- gpio_free(pd->detect_pin);
+error_out:
+ free(host);
+
+ if (gpio_is_valid(host->detect_pin))
+ gpio_free(host->detect_pin);
return ret;
}
+static __maybe_unused struct of_device_id atmci_compatible[] = {
+ {
+ .compatible = "atmel,hsmci",
+ }, {
+ /* sentinel */
+ }
+};
+
static struct driver_d atmci_driver = {
.name = "atmel_mci",
.probe = atmci_probe,
+ .of_compatible = DRV_OF_COMPAT(atmci_compatible),
};
device_platform_driver(atmci_driver);
diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c
index 055a5e2b06..b173a173b1 100644
--- a/drivers/mci/mci-core.c
+++ b/drivers/mci/mci-core.c
@@ -1621,8 +1621,10 @@ static int mci_register_partition(struct mci_part *part)
rc = 0; /* it's not a failure */
}
- if (np)
+ if (np) {
of_parse_partitions(&part->blk.cdev, np);
+ of_partitions_register_fixup(&part->blk.cdev);
+ }
return 0;
}
@@ -1843,20 +1845,18 @@ err_free:
return ret;
}
-void mci_of_parse(struct mci_host *host)
+void mci_of_parse_node(struct mci_host *host,
+ struct device_node *np)
{
- struct device_node *np;
u32 bus_width;
u32 dsr_val;
if (!IS_ENABLED(CONFIG_OFDEVICE))
return;
- if (!host->hw_dev || !host->hw_dev->device_node)
+ if (!host->hw_dev || !np)
return;
- np = host->hw_dev->device_node;
-
/* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */
if (of_property_read_u32(np, "bus-width", &bus_width) < 0) {
/* If bus-width is missing we get the driver's default, which
@@ -1897,6 +1897,11 @@ void mci_of_parse(struct mci_host *host)
host->non_removable = of_property_read_bool(np, "non-removable");
}
+void mci_of_parse(struct mci_host *host)
+{
+ return mci_of_parse_node(host, host->hw_dev->device_node);
+}
+
struct mci *mci_get_device_by_name(const char *name)
{
struct mci *mci;
diff --git a/drivers/misc/state.c b/drivers/misc/state.c
index b43aee60fe..98ed42e757 100644
--- a/drivers/misc/state.c
+++ b/drivers/misc/state.c
@@ -26,6 +26,7 @@ static int state_probe(struct device_d *dev)
struct device_node *np = dev->device_node;
struct state *state;
bool readonly = false;
+ int ret;
state = state_new_from_node(np, NULL, 0, 0, readonly);
if (IS_ERR(state)) {
@@ -35,6 +36,11 @@ static int state_probe(struct device_d *dev)
return ret;
}
+ ret = state_load(state);
+ if (ret)
+ dev_warn(dev, "Failed to load persistent state, continuing with defaults, %d\n",
+ ret);
+
return 0;
}
diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c
index 4e7bfdb3da..1eb8dd36d8 100644
--- a/drivers/mtd/core.c
+++ b/drivers/mtd/core.c
@@ -556,67 +556,6 @@ static int mtd_part_compare(struct list_head *a, struct list_head *b)
return 0;
}
-static int of_mtd_fixup(struct device_node *root, void *ctx)
-{
- struct mtd_info *mtd = ctx, *partmtd;
- struct device_node *np, *part, *tmp;
- int ret;
-
- np = of_find_node_by_path_from(root, mtd->of_path);
- if (!np) {
- dev_err(&mtd->class_dev, "Cannot find nodepath %s, cannot fixup\n",
- mtd->of_path);
- return -EINVAL;
- }
-
- for_each_child_of_node_safe(np, tmp, part) {
- if (of_get_property(part, "compatible", NULL))
- continue;
- of_delete_node(part);
- }
-
- list_for_each_entry(partmtd, &mtd->partitions, partitions_entry) {
- int na, ns, len = 0;
- char *name = basprintf("partition@%0llx",
- partmtd->master_offset);
- void *p;
- u8 tmp[16 * 16]; /* Up to 64-bit address + 64-bit size */
-
- if (!name)
- return -ENOMEM;
-
- part = of_new_node(np, name);
- free(name);
- if (!part)
- return -ENOMEM;
-
- p = of_new_property(part, "label", partmtd->cdev.partname,
- strlen(partmtd->cdev.partname) + 1);
- if (!p)
- return -ENOMEM;
-
- na = of_n_addr_cells(part);
- ns = of_n_size_cells(part);
-
- of_write_number(tmp + len, partmtd->master_offset, na);
- len += na * 4;
- of_write_number(tmp + len, partmtd->size, ns);
- len += ns * 4;
-
- ret = of_set_property(part, "reg", tmp, len, 1);
- if (ret)
- return ret;
-
- if (partmtd->cdev.flags & DEVFS_PARTITION_READONLY) {
- ret = of_set_property(part, "read-only", NULL, 0, 1);
- if (ret)
- return ret;
- }
- }
-
- return 0;
-}
-
static int mtd_detect(struct device_d *dev)
{
struct mtd_info *mtd = container_of(dev, struct mtd_info, class_dev);
@@ -732,7 +671,9 @@ int add_mtd_device(struct mtd_info *mtd, const char *devname, int device_id)
of_parse_partitions(&mtd->cdev, mtd->parent->device_node);
if (IS_ENABLED(CONFIG_OFDEVICE) && mtd->parent->device_node) {
mtd->of_path = xstrdup(mtd->parent->device_node->full_name);
- of_register_fixup(of_mtd_fixup, mtd);
+ ret = of_partitions_register_fixup(&mtd->cdev);
+ if (ret)
+ goto err1;
}
}
diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c
index 777cb758ce..013697732d 100644
--- a/drivers/mtd/partition.c
+++ b/drivers/mtd/partition.c
@@ -225,6 +225,8 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
if (ret)
goto err;
+ part->cdev.master = &part->master->cdev;
+
return part;
err:
free(part->cdev.partname);
diff --git a/drivers/mtd/peb.c b/drivers/mtd/peb.c
index 66227d4ffb..c35b63f2fd 100644
--- a/drivers/mtd/peb.c
+++ b/drivers/mtd/peb.c
@@ -565,6 +565,8 @@ int mtd_peb_create_bitflips(struct mtd_info *mtd, int pnum, int offset,
if (offset < 0 || offset + len > mtd->erasesize)
return -EINVAL;
+ if (offset % mtd->writesize)
+ return -EINVAL;
if (len <= 0)
return -EINVAL;
if (num_bitflips <= 0)
@@ -610,7 +612,7 @@ int mtd_peb_create_bitflips(struct mtd_info *mtd, int pnum, int offset,
for (i = 0; i < num_bitflips; i++) {
int offs;
int bit;
- u8 *pos = buf;
+ u8 *pos = buf + offset;
if (random) {
offs = random32() % len;
diff --git a/drivers/net/macb.c b/drivers/net/macb.c
index 5f2e5e5131..739a3dfbef 100644
--- a/drivers/net/macb.c
+++ b/drivers/net/macb.c
@@ -48,6 +48,7 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/phy.h>
+#include <of_net.h>
#include "macb.h"
@@ -615,14 +616,8 @@ static int macb_probe(struct device_d *dev)
struct resource *iores;
struct eth_device *edev;
struct macb_device *macb;
+ const char *pclk_name;
u32 ncfgr;
- struct macb_platform_data *pdata;
-
- if (!dev->platform_data) {
- dev_err(dev, "macb: no platform_data\n");
- return -ENODEV;
- }
- pdata = dev->platform_data;
edev = xzalloc(sizeof(struct eth_device) + sizeof(struct macb_device));
edev->priv = (struct macb_device *)(edev + 1);
@@ -633,22 +628,49 @@ static int macb_probe(struct device_d *dev)
edev->open = macb_open;
edev->send = macb_send;
edev->halt = macb_halt;
- edev->get_ethaddr = pdata->get_ethaddr ? pdata->get_ethaddr : macb_get_ethaddr;
+ edev->get_ethaddr = macb_get_ethaddr;
edev->set_ethaddr = macb_set_ethaddr;
edev->parent = dev;
macb->miibus.read = macb_phy_read;
macb->miibus.write = macb_phy_write;
- macb->phy_addr = pdata->phy_addr;
macb->miibus.priv = macb;
macb->miibus.parent = dev;
- if (pdata->phy_interface == PHY_INTERFACE_MODE_NA)
- macb->interface = PHY_INTERFACE_MODE_MII;
- else
- macb->interface = pdata->phy_interface;
+ if (dev->platform_data) {
+ struct macb_platform_data *pdata = dev->platform_data;
+
+ if (pdata->phy_interface == PHY_INTERFACE_MODE_NA)
+ macb->interface = PHY_INTERFACE_MODE_MII;
+ else
+ macb->interface = pdata->phy_interface;
+
+ if (pdata->get_ethaddr)
+ edev->get_ethaddr = pdata->get_ethaddr;
+
+ macb->phy_addr = pdata->phy_addr;
+ macb->phy_flags = pdata->phy_flags;
+ pclk_name = "macb_clk";
+ } else if (IS_ENABLED(CONFIG_OFDEVICE) && dev->device_node) {
+ int ret;
+ struct device_node *mdiobus;
- macb->phy_flags = pdata->phy_flags;
+ ret = of_get_phy_mode(dev->device_node);
+ if (ret < 0)
+ macb->interface = PHY_INTERFACE_MODE_MII;
+ else
+ macb->interface = ret;
+
+ mdiobus = of_get_child_by_name(dev->device_node, "mdio");
+ if (mdiobus)
+ macb->miibus.dev.device_node = mdiobus;
+
+ macb->phy_addr = -1;
+ pclk_name = NULL;
+ } else {
+ dev_err(dev, "macb: no platform_data\n");
+ return -ENODEV;
+ }
iores = dev_request_mem_resource(dev, 0);
if (IS_ERR(iores))
@@ -698,8 +720,14 @@ static int macb_probe(struct device_d *dev)
return 0;
}
+static const struct of_device_id macb_dt_ids[] = {
+ { .compatible = "cdns,at91sam9260-macb",},
+ { /* sentinel */ }
+};
+
static struct driver_d macb_driver = {
.name = "macb",
.probe = macb_probe,
+ .of_compatible = DRV_OF_COMPAT(macb_dt_ids),
};
device_platform_driver(macb_driver);
diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 38b2ad31f8..ea5a4a9be2 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -31,6 +31,22 @@
#define MII_M1011_PHY_STATUS_RESOLVED BIT(11)
#define MII_M1011_PHY_STATUS_LINK BIT(10)
+#define MII_M1111_PHY_LED_CONTROL 0x18
+#define MII_M1111_PHY_LED_DIRECT 0x4100
+#define MII_M1111_PHY_LED_COMBINE 0x411c
+#define MII_M1111_PHY_EXT_CR 0x14
+#define MII_M1111_RX_DELAY 0x80
+#define MII_M1111_TX_DELAY 0x2
+#define MII_M1111_PHY_EXT_SR 0x1b
+
+#define MII_M1111_HWCFG_MODE_MASK 0xf
+#define MII_M1111_HWCFG_MODE_COPPER_RGMII 0xb
+#define MII_M1111_HWCFG_MODE_FIBER_RGMII 0x3
+#define MII_M1111_HWCFG_MODE_SGMII_NO_CLK 0x4
+#define MII_M1111_HWCFG_MODE_COPPER_RTBI 0x9
+#define MII_M1111_HWCFG_FIBER_COPPER_AUTO 0x8000
+#define MII_M1111_HWCFG_FIBER_COPPER_RES 0x2000
+
#define MII_M1111_COPPER 0
#define MII_M1111_FIBER 1
@@ -402,6 +418,107 @@ static int m88e1540_config_init(struct phy_device *phydev)
return marvell_config_init(phydev);
}
+static int m88e1111_config_init(struct phy_device *phydev)
+{
+ int err;
+ int temp;
+
+ if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) ||
+ (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) ||
+ (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) ||
+ (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)) {
+
+ temp = phy_read(phydev, MII_M1111_PHY_EXT_CR);
+ if (temp < 0)
+ return temp;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
+ temp |= (MII_M1111_RX_DELAY | MII_M1111_TX_DELAY);
+ } else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+ temp &= ~MII_M1111_TX_DELAY;
+ temp |= MII_M1111_RX_DELAY;
+ } else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+ temp &= ~MII_M1111_RX_DELAY;
+ temp |= MII_M1111_TX_DELAY;
+ }
+
+ err = phy_write(phydev, MII_M1111_PHY_EXT_CR, temp);
+ if (err < 0)
+ return err;
+
+ temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+ if (temp < 0)
+ return temp;
+
+ temp &= ~(MII_M1111_HWCFG_MODE_MASK);
+
+ if (temp & MII_M1111_HWCFG_FIBER_COPPER_RES)
+ temp |= MII_M1111_HWCFG_MODE_FIBER_RGMII;
+ else
+ temp |= MII_M1111_HWCFG_MODE_COPPER_RGMII;
+
+ err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
+ if (err < 0)
+ return err;
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+ if (temp < 0)
+ return temp;
+
+ temp &= ~(MII_M1111_HWCFG_MODE_MASK);
+ temp |= MII_M1111_HWCFG_MODE_SGMII_NO_CLK;
+ temp |= MII_M1111_HWCFG_FIBER_COPPER_AUTO;
+
+ err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
+ if (err < 0)
+ return err;
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RTBI) {
+ temp = phy_read(phydev, MII_M1111_PHY_EXT_CR);
+ if (temp < 0)
+ return temp;
+ temp |= (MII_M1111_RX_DELAY | MII_M1111_TX_DELAY);
+ err = phy_write(phydev, MII_M1111_PHY_EXT_CR, temp);
+ if (err < 0)
+ return err;
+
+ temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+ if (temp < 0)
+ return temp;
+ temp &= ~(MII_M1111_HWCFG_MODE_MASK | MII_M1111_HWCFG_FIBER_COPPER_RES);
+ temp |= 0x7 | MII_M1111_HWCFG_FIBER_COPPER_AUTO;
+ err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
+ if (err < 0)
+ return err;
+
+ /* soft reset */
+ err = phy_write(phydev, MII_BMCR, BMCR_RESET);
+ if (err < 0)
+ return err;
+ do
+ temp = phy_read(phydev, MII_BMCR);
+ while (temp & BMCR_RESET);
+
+ temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+ if (temp < 0)
+ return temp;
+ temp &= ~(MII_M1111_HWCFG_MODE_MASK | MII_M1111_HWCFG_FIBER_COPPER_RES);
+ temp |= MII_M1111_HWCFG_MODE_COPPER_RTBI | MII_M1111_HWCFG_FIBER_COPPER_AUTO;
+ err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
+ if (err < 0)
+ return err;
+ }
+
+ err = marvell_of_reg_init(phydev);
+ if (err < 0)
+ return err;
+
+ return phy_write(phydev, MII_BMCR, BMCR_RESET);
+}
+
static int m88e1121_config_init(struct phy_device *phydev)
{
u16 reg;
@@ -502,6 +619,15 @@ static int m88e1510_config_init(struct phy_device *phydev)
static struct phy_driver marvell_drivers[] = {
{
+ .phy_id = MARVELL_PHY_ID_88E1111,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .drv.name = "Marvell 88E1111",
+ .features = PHY_GBIT_FEATURES,
+ .config_init = &m88e1111_config_init,
+ .config_aneg = &genphy_config_aneg,
+ .read_status = &marvell_read_status,
+ },
+ {
.phy_id = MARVELL_PHY_ID_88E1121R,
.phy_id_mask = MARVELL_PHY_ID_MASK,
.drv.name = "Marvell 88E1121R",
diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c
index a839f2dee8..a846fb28e2 100644
--- a/drivers/net/phy/mdio-gpio.c
+++ b/drivers/net/phy/mdio-gpio.c
@@ -45,7 +45,7 @@ struct mdio_gpio_info {
int mdc_active_low, mdio_active_low, mdo_active_low;
};
-struct mdio_gpio_info *mdio_gpio_of_get_info(struct device_d *dev)
+static struct mdio_gpio_info *mdio_gpio_of_get_info(struct device_d *dev)
{
int ret;
enum of_gpio_flags flags;
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
new file mode 100644
index 0000000000..d801cc25af
--- /dev/null
+++ b/drivers/nvmem/Kconfig
@@ -0,0 +1,18 @@
+menuconfig NVMEM
+ bool "NVMEM Support"
+ help
+ Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
+
+ This framework is designed to provide a generic interface to NVMEM
+
+ If unsure, say no.
+
+if NVMEM
+
+config NVMEM_SNVS_LPGPR
+ tristate "Freescale SNVS LPGPR support"
+ select MFD_SYSCON
+ help
+ If you say yes here you get NVMEM support for the Freescale SNVS
+ Low Power Generic Purpose Register (LPGPR).
+endif
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
new file mode 100644
index 0000000000..32522e9fbf
--- /dev/null
+++ b/drivers/nvmem/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for nvmem drivers.
+#
+
+obj-$(CONFIG_NVMEM) += nvmem_core.o
+nvmem_core-y := core.o
+
+# Devices
+obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o
+nvmem_snvs_lpgpr-y := snvs_lpgpr.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
new file mode 100644
index 0000000000..4e50a8843f
--- /dev/null
+++ b/drivers/nvmem/core.c
@@ -0,0 +1,764 @@
+/*
+ * nvmem framework core.
+ *
+ * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+ * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <common.h>
+#include <libbb.h>
+#include <malloc.h>
+#include <of.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+
+struct nvmem_device {
+ const char *name;
+ struct device_d dev;
+ const struct nvmem_bus *bus;
+ struct list_head node;
+ int stride;
+ int word_size;
+ int ncells;
+ int users;
+ size_t size;
+ bool read_only;
+ struct cdev cdev;
+};
+
+struct nvmem_cell {
+ const char *name;
+ int offset;
+ int bytes;
+ int bit_offset;
+ int nbits;
+ struct nvmem_device *nvmem;
+ struct list_head node;
+};
+
+static LIST_HEAD(nvmem_cells);
+static LIST_HEAD(nvmem_devs);
+
+int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset,
+ size_t bytes, void *buf);
+int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset,
+ size_t bytes, const void *buf);
+
+
+static ssize_t nvmem_cdev_read(struct cdev *cdev, void *buf, size_t count,
+ loff_t offset, unsigned long flags)
+{
+ struct nvmem_device *nvmem = container_of(cdev, struct nvmem_device, cdev);
+ ssize_t retlen;
+
+ dev_dbg(cdev->dev, "read ofs: 0x%08llx count: 0x%08zx\n",
+ offset, count);
+
+ retlen = nvmem_device_read(nvmem, offset, count, buf);
+
+ return retlen;
+}
+
+static ssize_t nvmem_cdev_write(struct cdev *cdev, const void *buf, size_t count,
+ loff_t offset, unsigned long flags)
+{
+ struct nvmem_device *nvmem = container_of(cdev, struct nvmem_device, cdev);
+ ssize_t retlen;
+
+ dev_dbg(cdev->dev, "write ofs: 0x%08llx count: 0x%08zx\n",
+ offset, count);
+
+ retlen = nvmem_device_write(nvmem, offset, count, buf);
+
+ return retlen;
+}
+
+static struct file_operations nvmem_chrdev_ops = {
+ .read = nvmem_cdev_read,
+ .write = nvmem_cdev_write,
+ .lseek = dev_lseek_default,
+};
+
+static int nvmem_register_cdev(struct nvmem_device *nvmem)
+{
+ struct device_d *dev = &nvmem->dev;
+ const char *alias;
+ char *devname;
+ int err;
+
+ alias = of_alias_get(dev->device_node);
+ if (alias) {
+ devname = xstrdup(alias);
+ } else {
+ err = cdev_find_free_index("nvmem");
+ if (err < 0) {
+ dev_err(dev, "no index found to name device\n");
+ return err;
+ }
+ devname = xasprintf("nvmem%d", err);
+ }
+
+ nvmem->cdev.name = devname;
+ nvmem->cdev.flags = DEVFS_IS_CHARACTER_DEV;
+ nvmem->cdev.ops = &nvmem_chrdev_ops;
+ nvmem->cdev.dev = &nvmem->dev;
+ nvmem->cdev.size = nvmem->size;
+
+ return devfs_create(&nvmem->cdev);
+}
+
+static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np)
+{
+ struct nvmem_device *dev;
+
+ if (!nvmem_np)
+ return NULL;
+
+ list_for_each_entry(dev, &nvmem_devs, node)
+ if (dev->dev.device_node->name && !strcmp(dev->dev.device_node->name, nvmem_np->name))
+ return dev;
+
+ return NULL;
+}
+
+static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
+{
+ struct nvmem_cell *p;
+
+ list_for_each_entry(p, &nvmem_cells, node)
+ if (p && !strcmp(p->name, cell_id))
+ return p;
+
+ return NULL;
+}
+
+static void nvmem_cell_drop(struct nvmem_cell *cell)
+{
+ list_del(&cell->node);
+ kfree(cell);
+}
+
+static void nvmem_cell_add(struct nvmem_cell *cell)
+{
+ list_add_tail(&cell->node, &nvmem_cells);
+}
+
+static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info,
+ struct nvmem_cell *cell)
+{
+ cell->nvmem = nvmem;
+ cell->offset = info->offset;
+ cell->bytes = info->bytes;
+ cell->name = info->name;
+
+ cell->bit_offset = info->bit_offset;
+ cell->nbits = info->nbits;
+
+ if (cell->nbits)
+ cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
+ BITS_PER_BYTE);
+
+ if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
+ dev_err(&nvmem->dev,
+ "cell %s unaligned to nvmem stride %d\n",
+ cell->name, nvmem->stride);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * nvmem_register() - Register a nvmem device for given nvmem_config.
+ *
+ * @config: nvmem device configuration with which nvmem device is created.
+ *
+ * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device
+ * on success.
+ */
+
+struct nvmem_device *nvmem_register(const struct nvmem_config *config)
+{
+ struct nvmem_device *nvmem;
+ struct device_node *np;
+ int rval;
+
+ if (!config->dev)
+ return ERR_PTR(-EINVAL);
+
+ nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL);
+ if (!nvmem)
+ return ERR_PTR(-ENOMEM);
+
+ nvmem->stride = config->stride;
+ nvmem->word_size = config->word_size;
+ nvmem->size = config->size;
+ nvmem->dev.parent = config->dev;
+ nvmem->bus = config->bus;
+ np = config->dev->device_node;
+ nvmem->dev.device_node = np;
+
+ nvmem->read_only = of_property_read_bool(np, "read-only") |
+ config->read_only;
+
+ safe_strncpy(nvmem->dev.name, config->name, MAX_DRIVER_NAME);
+ nvmem->dev.id = DEVICE_ID_DYNAMIC;
+
+ dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
+
+ rval = register_device(&nvmem->dev);
+ if (rval) {
+ kfree(nvmem);
+ return ERR_PTR(rval);
+ }
+
+ rval = nvmem_register_cdev(nvmem);
+ if (rval) {
+ kfree(nvmem);
+ return ERR_PTR(rval);
+ }
+
+ list_add_tail(&nvmem->node, &nvmem_devs);
+
+ return nvmem;
+}
+EXPORT_SYMBOL_GPL(nvmem_register);
+
+static struct nvmem_device *__nvmem_device_get(struct device_node *np,
+ struct nvmem_cell **cellp,
+ const char *cell_id)
+{
+ struct nvmem_device *nvmem = NULL;
+
+ if (np) {
+ nvmem = of_nvmem_find(np);
+ if (!nvmem)
+ return ERR_PTR(-EPROBE_DEFER);
+ } else {
+ struct nvmem_cell *cell = nvmem_find_cell(cell_id);
+
+ if (cell) {
+ nvmem = cell->nvmem;
+ *cellp = cell;
+ }
+
+ if (!nvmem)
+ return ERR_PTR(-ENOENT);
+ }
+
+ nvmem->users++;
+
+ return nvmem;
+}
+
+static void __nvmem_device_put(struct nvmem_device *nvmem)
+{
+ nvmem->users--;
+}
+
+static struct nvmem_device *nvmem_find(const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OFTREE)
+/**
+ * of_nvmem_device_get() - Get nvmem device from a given id
+ *
+ * @dev node: Device tree node that uses the nvmem device
+ * @id: nvmem name from nvmem-names property.
+ *
+ * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
+ * on success.
+ */
+struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
+{
+
+ struct device_node *nvmem_np;
+ int index;
+
+ index = of_property_match_string(np, "nvmem-names", id);
+
+ nvmem_np = of_parse_phandle(np, "nvmem", index);
+ if (!nvmem_np)
+ return ERR_PTR(-EINVAL);
+
+ return __nvmem_device_get(nvmem_np, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(of_nvmem_device_get);
+#endif
+
+/**
+ * nvmem_device_get() - Get nvmem device from a given id
+ *
+ * @dev : Device that uses the nvmem device
+ * @id: nvmem name from nvmem-names property.
+ *
+ * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
+ * on success.
+ */
+struct nvmem_device *nvmem_device_get(struct device_d *dev, const char *dev_name)
+{
+ if (dev->device_node) { /* try dt first */
+ struct nvmem_device *nvmem;
+
+ nvmem = of_nvmem_device_get(dev->device_node, dev_name);
+
+ if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER)
+ return nvmem;
+
+ }
+
+ return nvmem_find(dev_name);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_get);
+
+/**
+ * nvmem_device_put() - put alredy got nvmem device
+ *
+ * @nvmem: pointer to nvmem device that needs to be released.
+ */
+void nvmem_device_put(struct nvmem_device *nvmem)
+{
+ __nvmem_device_put(nvmem);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_put);
+
+static struct nvmem_cell *nvmem_cell_get_from_list(const char *cell_id)
+{
+ struct nvmem_cell *cell = NULL;
+ struct nvmem_device *nvmem;
+
+ nvmem = __nvmem_device_get(NULL, &cell, cell_id);
+ if (IS_ERR(nvmem))
+ return ERR_CAST(nvmem);
+
+ return cell;
+}
+
+#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OFTREE)
+/**
+ * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
+ *
+ * @dev node: Device tree node that uses the nvmem cell
+ * @id: nvmem cell name from nvmem-cell-names property.
+ *
+ * Return: Will be an ERR_PTR() on error or a valid pointer
+ * to a struct nvmem_cell. The nvmem_cell will be freed by the
+ * nvmem_cell_put().
+ */
+struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
+ const char *name)
+{
+ struct device_node *cell_np, *nvmem_np;
+ struct nvmem_cell *cell;
+ struct nvmem_device *nvmem;
+ const __be32 *addr;
+ int rval, len, index;
+
+ index = of_property_match_string(np, "nvmem-cell-names", name);
+
+ cell_np = of_parse_phandle(np, "nvmem-cells", index);
+ if (!cell_np)
+ return ERR_PTR(-EINVAL);
+
+ nvmem_np = of_get_parent(cell_np);
+ if (!nvmem_np)
+ return ERR_PTR(-EINVAL);
+
+ nvmem = __nvmem_device_get(nvmem_np, NULL, NULL);
+ if (IS_ERR(nvmem))
+ return ERR_CAST(nvmem);
+
+ addr = of_get_property(cell_np, "reg", &len);
+ if (!addr || (len < 2 * sizeof(u32))) {
+ dev_err(&nvmem->dev, "nvmem: invalid reg on %s\n",
+ cell_np->full_name);
+ rval = -EINVAL;
+ goto err_mem;
+ }
+
+ cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+ if (!cell) {
+ rval = -ENOMEM;
+ goto err_mem;
+ }
+
+ cell->nvmem = nvmem;
+ cell->offset = be32_to_cpup(addr++);
+ cell->bytes = be32_to_cpup(addr);
+ cell->name = cell_np->name;
+
+ addr = of_get_property(cell_np, "bits", &len);
+ if (addr && len == (2 * sizeof(u32))) {
+ cell->bit_offset = be32_to_cpup(addr++);
+ cell->nbits = be32_to_cpup(addr);
+ }
+
+ if (cell->nbits)
+ cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
+ BITS_PER_BYTE);
+
+ if (cell->bytes < nvmem->word_size)
+ cell->bytes = nvmem->word_size;
+
+ if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
+ dev_err(&nvmem->dev,
+ "cell %s unaligned to nvmem stride %d\n",
+ cell->name, nvmem->stride);
+ rval = -EINVAL;
+ goto err_sanity;
+ }
+
+ nvmem_cell_add(cell);
+
+ return cell;
+
+err_sanity:
+ kfree(cell);
+
+err_mem:
+ __nvmem_device_put(nvmem);
+
+ return ERR_PTR(rval);
+}
+EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
+#endif
+
+/**
+ * nvmem_cell_get() - Get nvmem cell of device form a given cell name
+ *
+ * @dev node: Device tree node that uses the nvmem cell
+ * @id: nvmem cell name to get.
+ *
+ * Return: Will be an ERR_PTR() on error or a valid pointer
+ * to a struct nvmem_cell. The nvmem_cell will be freed by the
+ * nvmem_cell_put().
+ */
+struct nvmem_cell *nvmem_cell_get(struct device_d *dev, const char *cell_id)
+{
+ struct nvmem_cell *cell;
+
+ if (dev->device_node) { /* try dt first */
+ cell = of_nvmem_cell_get(dev->device_node, cell_id);
+ if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER)
+ return cell;
+ }
+
+ return nvmem_cell_get_from_list(cell_id);
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_get);
+
+/**
+ * nvmem_cell_put() - Release previously allocated nvmem cell.
+ *
+ * @cell: Previously allocated nvmem cell by nvmem_cell_get()
+ */
+void nvmem_cell_put(struct nvmem_cell *cell)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+
+ __nvmem_device_put(nvmem);
+ nvmem_cell_drop(cell);
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_put);
+
+static inline void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell,
+ void *buf)
+{
+ u8 *p, *b;
+ int i, bit_offset = cell->bit_offset;
+
+ p = b = buf;
+ if (bit_offset) {
+ /* First shift */
+ *b++ >>= bit_offset;
+
+ /* setup rest of the bytes if any */
+ for (i = 1; i < cell->bytes; i++) {
+ /* Get bits from next byte and shift them towards msb */
+ *p |= *b << (BITS_PER_BYTE - bit_offset);
+
+ p = b;
+ *b++ >>= bit_offset;
+ }
+
+ /* result fits in less bytes */
+ if (cell->bytes != DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE))
+ *p-- = 0;
+ }
+ /* clear msb bits if any leftover in the last byte */
+ *p &= GENMASK((cell->nbits%BITS_PER_BYTE) - 1, 0);
+}
+
+static int __nvmem_cell_read(struct nvmem_device *nvmem,
+ struct nvmem_cell *cell,
+ void *buf, size_t *len)
+{
+ int rc;
+
+ rc = nvmem->bus->read(&nvmem->dev, cell->offset, buf, cell->bytes);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ /* shift bits in-place */
+ if (cell->bit_offset || cell->nbits)
+ nvmem_shift_read_buffer_in_place(cell, buf);
+
+ *len = cell->bytes;
+
+ return 0;
+}
+
+/**
+ * nvmem_cell_read() - Read a given nvmem cell
+ *
+ * @cell: nvmem cell to be read.
+ * @len: pointer to length of cell which will be populated on successful read.
+ *
+ * Return: ERR_PTR() on error or a valid pointer to a char * buffer on success.
+ * The buffer should be freed by the consumer with a kfree().
+ */
+void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+ u8 *buf;
+ int rc;
+
+ if (!nvmem)
+ return ERR_PTR(-EINVAL);
+
+ buf = kzalloc(cell->bytes, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ rc = __nvmem_cell_read(nvmem, cell, buf, len);
+ if (IS_ERR_VALUE(rc)) {
+ kfree(buf);
+ return ERR_PTR(rc);
+ }
+
+ return buf;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_read);
+
+static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell,
+ u8 *_buf, int len)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+ int i, rc, nbits, bit_offset = cell->bit_offset;
+ u8 v, *p, *buf, *b, pbyte, pbits;
+
+ nbits = cell->nbits;
+ buf = kzalloc(cell->bytes, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(buf, _buf, len);
+ p = b = buf;
+
+ if (bit_offset) {
+ pbyte = *b;
+ *b <<= bit_offset;
+
+ /* setup the first byte with lsb bits from nvmem */
+ rc = nvmem->bus->read(&nvmem->dev, cell->offset, &v, 1);
+ *b++ |= GENMASK(bit_offset - 1, 0) & v;
+
+ /* setup rest of the byte if any */
+ for (i = 1; i < cell->bytes; i++) {
+ /* Get last byte bits and shift them towards lsb */
+ pbits = pbyte >> (BITS_PER_BYTE - 1 - bit_offset);
+ pbyte = *b;
+ p = b;
+ *b <<= bit_offset;
+ *b++ |= pbits;
+ }
+ }
+
+ /* if it's not end on byte boundary */
+ if ((nbits + bit_offset) % BITS_PER_BYTE) {
+ /* setup the last byte with msb bits from nvmem */
+ rc = nvmem->bus->read(&nvmem->dev, cell->offset + cell->bytes - 1,
+ &v, 1);
+ *p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v;
+
+ }
+
+ return buf;
+}
+
+/**
+ * nvmem_cell_write() - Write to a given nvmem cell
+ *
+ * @cell: nvmem cell to be written.
+ * @buf: Buffer to be written.
+ * @len: length of buffer to be written to nvmem cell.
+ *
+ * Return: length of bytes written or negative on failure.
+ */
+int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+ int rc;
+
+ if (!nvmem || nvmem->read_only ||
+ (cell->bit_offset == 0 && len != cell->bytes))
+ return -EINVAL;
+
+ if (cell->bit_offset || cell->nbits) {
+ buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ }
+
+ rc = nvmem->bus->write(&nvmem->dev, cell->offset, buf, cell->bytes);
+
+ /* free the tmp buffer */
+ if (cell->bit_offset || cell->nbits)
+ kfree(buf);
+
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_write);
+
+/**
+ * nvmem_device_cell_read() - Read a given nvmem device and cell
+ *
+ * @nvmem: nvmem device to read from.
+ * @info: nvmem cell info to be read.
+ * @buf: buffer pointer which will be populated on successful read.
+ *
+ * Return: length of successful bytes read on success and negative
+ * error code on error.
+ */
+ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info, void *buf)
+{
+ struct nvmem_cell cell;
+ int rc;
+ ssize_t len;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ rc = __nvmem_cell_read(nvmem, &cell, buf, &len);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_cell_read);
+
+/**
+ * nvmem_device_cell_write() - Write cell to a given nvmem device
+ *
+ * @nvmem: nvmem device to be written to.
+ * @info: nvmem cell info to be written
+ * @buf: buffer to be written to cell.
+ *
+ * Return: length of bytes written or negative error code on failure.
+ * */
+int nvmem_device_cell_write(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info, void *buf)
+{
+ struct nvmem_cell cell;
+ int rc;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return nvmem_cell_write(&cell, buf, cell.bytes);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_cell_write);
+
+/**
+ * nvmem_device_read() - Read from a given nvmem device
+ *
+ * @nvmem: nvmem device to read from.
+ * @offset: offset in nvmem device.
+ * @bytes: number of bytes to read.
+ * @buf: buffer pointer which will be populated on successful read.
+ *
+ * Return: length of successful bytes read on success and negative
+ * error code on error.
+ */
+int nvmem_device_read(struct nvmem_device *nvmem,
+ unsigned int offset,
+ size_t bytes, void *buf)
+{
+ int rc;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ if (offset >= nvmem->size || bytes > nvmem->size - offset)
+ return -EINVAL;
+
+ if (!bytes)
+ return 0;
+
+ rc = nvmem->bus->read(&nvmem->dev, offset, buf, bytes);
+
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return bytes;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_read);
+
+/**
+ * nvmem_device_write() - Write cell to a given nvmem device
+ *
+ * @nvmem: nvmem device to be written to.
+ * @offset: offset in nvmem device.
+ * @bytes: number of bytes to write.
+ * @buf: buffer to be written.
+ *
+ * Return: length of bytes written or negative error code on failure.
+ * */
+int nvmem_device_write(struct nvmem_device *nvmem,
+ unsigned int offset,
+ size_t bytes, const void *buf)
+{
+ int rc;
+
+ if (!nvmem || nvmem->read_only)
+ return -EINVAL;
+
+ if (offset >= nvmem->size || bytes > nvmem->size - offset)
+ return -EINVAL;
+
+ if (!bytes)
+ return 0;
+
+ rc = nvmem->bus->write(&nvmem->dev, offset, buf, bytes);
+
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+
+ return bytes;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_write);
diff --git a/drivers/nvmem/snvs_lpgpr.c b/drivers/nvmem/snvs_lpgpr.c
new file mode 100644
index 0000000000..74118a6b75
--- /dev/null
+++ b/drivers/nvmem/snvs_lpgpr.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
+ * Copyright (c) 2017 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ */
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <malloc.h>
+#include <regmap.h>
+#include <mfd/syscon.h>
+#include <linux/nvmem-provider.h>
+
+struct snvs_lpgpr_priv {
+ struct device_d *dev;
+ struct regmap *regmap;
+ int offset;
+ struct nvmem_config cfg;
+};
+
+static int snvs_lpgpr_write(struct device_d *dev, const int reg,
+ const void *_val, int bytes)
+{
+ struct snvs_lpgpr_priv *priv = dev->parent->priv;
+ const u32 *val = _val;
+ int i = 0, words = bytes / 4;
+
+ while (words--)
+ regmap_write(priv->regmap, priv->offset + reg + (i++ * 4),
+ *val++);
+
+ return 0;
+}
+
+static int snvs_lpgpr_read(struct device_d *dev, const int reg, void *_val,
+ int bytes)
+{
+ struct snvs_lpgpr_priv *priv = dev->parent->priv;
+ u32 *val = _val;
+ int i = 0, words = bytes / 4;
+
+ while (words--)
+ regmap_read(priv->regmap, priv->offset + reg + (i++ * 4),
+ val++);
+
+
+ return 0;
+}
+
+static const struct nvmem_bus snvs_lpgpr_nvmem_bus = {
+ .write = snvs_lpgpr_write,
+ .read = snvs_lpgpr_read,
+};
+
+static int snvs_lpgpr_probe(struct device_d *dev)
+{
+ struct device_node *node = dev->device_node;
+ struct snvs_lpgpr_priv *priv;
+ struct nvmem_config *cfg;
+ struct nvmem_device *nvmem;
+ int err;
+
+ if (!node)
+ return -ENOENT;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = syscon_node_to_regmap(of_get_parent(node));
+ if (IS_ERR(priv->regmap)) {
+ free(priv);
+ return PTR_ERR(priv->regmap);
+ }
+
+ err = of_property_read_u32(node, "offset", &priv->offset);
+ if (err)
+ return err;
+
+ cfg = &priv->cfg;
+ cfg->name = dev_name(dev);
+ cfg->dev = dev;
+ cfg->stride = 4,
+ cfg->word_size = 4,
+ cfg->size = 4,
+ cfg->bus = &snvs_lpgpr_nvmem_bus,
+
+ nvmem = nvmem_register(cfg);
+ if (IS_ERR(nvmem)) {
+ free(priv);
+ return PTR_ERR(nvmem);
+ }
+
+ dev->priv = priv;
+
+ return 0;
+}
+
+static __maybe_unused struct of_device_id snvs_lpgpr_dt_ids[] = {
+ { .compatible = "fsl,imx6sl-snvs-lpgpr", },
+ { .compatible = "fsl,imx6q-snvs-lpgpr", },
+ { },
+};
+
+static struct driver_d snvs_lpgpr_driver = {
+ .name = "nvmem-snvs-lpgpr",
+ .probe = snvs_lpgpr_probe,
+ .of_compatible = DRV_OF_COMPAT(snvs_lpgpr_dt_ids),
+};
+device_platform_driver(snvs_lpgpr_driver);
diff --git a/drivers/of/base.c b/drivers/of/base.c
index bef8f1de1a..6632f4d9dd 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -1217,6 +1217,28 @@ int of_property_write_u64_array(struct device_node *np,
}
/**
+ * of_property_write_string - Write a string to a property. If
+ * the property does not exist, it will be created and appended to the given
+ * device node.
+ *
+ * @np: device node to which the property value is to be written.
+ * @propname: name of the property to be written.
+ * value: pointer to the string to write
+ *
+ * Search for a property in a device node and write a string to
+ * it. If the property does not exist, it will be created and appended to
+ * the device node. Returns 0 on success, -ENOMEM if the property or array
+ * of elements cannot be created.
+ */
+int of_property_write_string(struct device_node *np,
+ const char *propname, const char *value)
+{
+ size_t len = strlen(value);
+
+ return of_set_property(np, propname, value, len + 1, 1);
+}
+
+/**
* of_parse_phandle_from - Resolve a phandle property to a device_node pointer from
* a given root node
* @np: Pointer to device node holding phandle property
@@ -2093,7 +2115,7 @@ int of_device_enable_path(const char *path)
*/
int of_device_disable(struct device_node *node)
{
- return of_set_property(node, "status", "disabled", sizeof("disabled"), 1);
+ return of_property_write_string(node, "status", "disabled");
}
/**
diff --git a/drivers/of/of_path.c b/drivers/of/of_path.c
index e0b2dc1247..a5886dffac 100644
--- a/drivers/of/of_path.c
+++ b/drivers/of/of_path.c
@@ -104,6 +104,113 @@ int of_find_path_by_node(struct device_node *node, char **outpath, unsigned flag
}
/**
+ * of_find_node_by_devpath - translate a device path to a device tree node
+ *
+ * @root: The device tree root. Can be NULL, in this case the internal tree is used
+ * @path: The path to look the node up for. Can be "/dev/cdevname" or "cdevname" directly.
+ *
+ * This is the counterpart of of_find_path_by_node(). Given a path this function tries
+ * to find the corresponding node in the given device tree.
+ *
+ * We first have to find the hardware device in the tree we are passed and then find
+ * a partition matching offset/size in this tree. This is necessary because the
+ * passed tree may use another partition binding (legacy vs. fixed-partitions). Also
+ * the node names may differ (some device trees have partition@<num> instead of
+ * partition@<offset>.
+ */
+struct device_node *of_find_node_by_devpath(struct device_node *root, const char *path)
+{
+ struct cdev *cdev;
+ bool is_partition = false;
+ struct device_node *np, *partnode, *rnp;
+ loff_t part_offset = 0, part_size = 0;
+
+ pr_debug("%s: looking for path %s\n", __func__, path);
+
+ if (!strncmp(path, "/dev/", 5))
+ path += 5;
+
+ cdev = cdev_by_name(path);
+ if (!cdev) {
+ pr_debug("%s: cdev %s not found\n", __func__, path);
+ return NULL;
+ }
+
+ /*
+ * Look for the device node of the master device (the one of_parse_partitions() has
+ * been called with
+ */
+ if (cdev->master) {
+ is_partition = true;
+ part_offset = cdev->offset;
+ part_size = cdev->size;
+ pr_debug("%s path %s: is a partition with offset 0x%08llx, size 0x%08llx\n",
+ __func__, path, part_offset, part_size);
+ np = cdev->master->device_node;
+ } else {
+ np = cdev->device_node;
+ }
+
+ /*
+ * Now find the device node of the master device in the device tree we have
+ * been passed.
+ */
+ rnp = of_find_node_by_path_from(root, np->full_name);
+ if (!rnp) {
+ pr_debug("%s path %s: %s not found in passed tree\n", __func__, path,
+ np->full_name);
+ return NULL;
+ }
+
+ if (!is_partition) {
+ pr_debug("%s path %s: returning full device node %s\n", __func__, path,
+ rnp->full_name);
+ return rnp;
+ }
+
+ /*
+ * Look for a partition with matching offset/size in the device node of
+ * the tree we have been passed.
+ */
+ partnode = of_get_child_by_name(rnp, "partitions");
+ if (!partnode) {
+ pr_debug("%s path %s: using legacy partition binding\n", __func__, path);
+ partnode = rnp;
+ }
+
+ for_each_child_of_node(partnode, np) {
+ const __be32 *reg;
+ int na, ns, len;
+ loff_t offset, size;
+
+ reg = of_get_property(np, "reg", &len);
+ if (!reg)
+ return NULL;
+
+ na = of_n_addr_cells(np);
+ ns = of_n_size_cells(np);
+
+ if (len < (na + ns) * sizeof(__be32)) {
+ pr_err("reg property too small in %s\n", np->full_name);
+ continue;
+ }
+
+ offset = of_read_number(reg, na);
+ size = of_read_number(reg + na, ns);
+
+ if (part_offset == offset && part_size == size) {
+ pr_debug("%s path %s: found matching partition in %s\n", __func__, path,
+ np->full_name);
+ return np;
+ }
+ }
+
+ pr_debug("%s path %s: no matching node found\n", __func__, path);
+
+ return NULL;
+}
+
+/**
* of_find_path - translate a path description in the devicetree to a barebox
* path
*
diff --git a/drivers/of/partition.c b/drivers/of/partition.c
index 8c2aef2326..25b41cb012 100644
--- a/drivers/of/partition.c
+++ b/drivers/of/partition.c
@@ -23,6 +23,16 @@
#include <linux/mtd/mtd.h>
#include <linux/err.h>
#include <nand.h>
+#include <init.h>
+#include <globalvar.h>
+
+static unsigned int of_partition_binding;
+
+enum of_binding_name {
+ MTD_OF_BINDING_NEW,
+ MTD_OF_BINDING_LEGACY,
+ MTD_OF_BINDING_DONTTOUCH,
+};
struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node)
{
@@ -30,10 +40,11 @@ struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node)
char *filename;
struct cdev *new;
const __be32 *reg;
- unsigned long offset, size;
+ u64 offset, size;
const char *name;
int len;
unsigned long flags = 0;
+ int na, ns;
if (!node)
return NULL;
@@ -42,8 +53,16 @@ struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node)
if (!reg)
return NULL;
- offset = be32_to_cpu(reg[0]);
- size = be32_to_cpu(reg[1]);
+ na = of_n_addr_cells(node);
+ ns = of_n_size_cells(node);
+
+ if (len < (na + ns) * sizeof(__be32)) {
+ pr_err("reg property too small in %s\n", node->full_name);
+ return NULL;
+ }
+
+ offset = of_read_number(reg, na);
+ size = of_read_number(reg + na, ns);
partname = of_get_property(node, "label", &len);
if (!partname)
@@ -53,7 +72,7 @@ struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node)
name = (char *)partname;
- debug("add partition: %s.%s 0x%08lx 0x%08lx\n", cdev->name, partname, offset, size);
+ debug("add partition: %s.%s 0x%08llx 0x%08llx\n", cdev->name, partname, offset, size);
if (of_get_property(node, "read-only", &len))
flags = DEVFS_PARTITION_READONLY;
@@ -79,6 +98,8 @@ int of_parse_partitions(struct cdev *cdev, struct device_node *node)
if (!node)
return -EINVAL;
+ cdev->device_node = node;
+
subnode = of_get_child_by_name(node, "partitions");
if (subnode) {
if (!of_device_is_compatible(subnode, "fixed-partitions"))
@@ -92,3 +113,150 @@ int of_parse_partitions(struct cdev *cdev, struct device_node *node)
return 0;
}
+
+static void delete_subnodes(struct device_node *np)
+{
+ struct device_node *part, *tmp;
+
+ for_each_child_of_node_safe(np, tmp, part) {
+ if (of_get_property(part, "compatible", NULL))
+ continue;
+
+ of_delete_node(part);
+ }
+}
+
+static int of_partition_fixup(struct device_node *root, void *ctx)
+{
+ struct cdev *cdev = ctx, *partcdev;
+ struct device_node *np, *part, *partnode;
+ int ret;
+ int n_cells, n_parts = 0;
+
+ if (of_partition_binding == MTD_OF_BINDING_DONTTOUCH)
+ return 0;
+
+ if (!cdev->device_node)
+ return -EINVAL;
+
+ list_for_each_entry(partcdev, &cdev->partitions, partition_entry) {
+ if (partcdev->flags & DEVFS_PARTITION_FROM_TABLE)
+ continue;
+ n_parts++;
+ }
+
+ if (!n_parts)
+ return 0;
+
+ if (cdev->size >= 0x100000000)
+ n_cells = 2;
+ else
+ n_cells = 1;
+
+ np = of_find_node_by_path_from(root, cdev->device_node->full_name);
+ if (!np) {
+ dev_err(cdev->dev, "Cannot find nodepath %s, cannot fixup\n",
+ cdev->device_node->full_name);
+ return -EINVAL;
+ }
+
+ partnode = of_get_child_by_name(np, "partitions");
+ if (partnode) {
+ if (of_partition_binding == MTD_OF_BINDING_LEGACY) {
+ of_delete_node(partnode);
+ partnode = np;
+ }
+ delete_subnodes(partnode);
+ } else {
+ delete_subnodes(np);
+
+ if (of_partition_binding == MTD_OF_BINDING_LEGACY)
+ partnode = np;
+ else
+ partnode = of_new_node(np, "partitions");
+ }
+
+ if (of_partition_binding == MTD_OF_BINDING_NEW) {
+ ret = of_property_write_string(partnode, "compatible",
+ "fixed-partitions");
+ if (ret)
+ return ret;
+ }
+
+ of_property_write_u32(partnode, "#size-cells", n_cells);
+ if (ret)
+ return ret;
+
+ of_property_write_u32(partnode, "#addres-cells", n_cells);
+ if (ret)
+ return ret;
+
+ list_for_each_entry(partcdev, &cdev->partitions, partition_entry) {
+ int na, ns, len = 0;
+ char *name;
+ void *p;
+ u8 tmp[16 * 16]; /* Up to 64-bit address + 64-bit size */
+ loff_t partoffset;
+
+ if (partcdev->flags & DEVFS_PARTITION_FROM_TABLE)
+ continue;
+
+ if (partcdev->mtd)
+ partoffset = partcdev->mtd->master_offset;
+ else
+ partoffset = partcdev->offset;
+
+ name = basprintf("partition@%0llx", partoffset);
+ if (!name)
+ return -ENOMEM;
+
+ part = of_new_node(partnode, name);
+ free(name);
+ if (!part)
+ return -ENOMEM;
+
+ p = of_new_property(part, "label", partcdev->partname,
+ strlen(partcdev->partname) + 1);
+ if (!p)
+ return -ENOMEM;
+
+ na = of_n_addr_cells(part);
+ ns = of_n_size_cells(part);
+
+ of_write_number(tmp + len, partoffset, na);
+ len += na * 4;
+ of_write_number(tmp + len, partcdev->size, ns);
+ len += ns * 4;
+
+ ret = of_set_property(part, "reg", tmp, len, 1);
+ if (ret)
+ return ret;
+
+ if (partcdev->flags & DEVFS_PARTITION_READONLY) {
+ ret = of_set_property(part, "read-only", NULL, 0, 1);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int of_partitions_register_fixup(struct cdev *cdev)
+{
+ return of_register_fixup(of_partition_fixup, cdev);
+}
+
+static const char *of_binding_names[] = {
+ "new", "legacy", "donttouch"
+};
+
+static int of_partition_init(void)
+{
+ dev_add_param_enum(&global_device, "of_partition_binding", NULL, NULL,
+ &of_partition_binding, of_binding_names,
+ ARRAY_SIZE(of_binding_names), NULL);
+
+ return 0;
+}
+device_initcall(of_partition_init);
diff --git a/drivers/serial/atmel.c b/drivers/serial/atmel.c
index ab94fd2177..2f8adc989f 100644
--- a/drivers/serial/atmel.c
+++ b/drivers/serial/atmel.c
@@ -446,8 +446,15 @@ static int atmel_serial_probe(struct device_d *dev)
return 0;
}
+static const struct of_device_id __maybe_unused atmel_serial_dt_ids[] = {
+ { .compatible = "atmel,at91rm9200-usart" },
+ { .compatible = "atmel,at91sam9260-usart" },
+ { /* sentinel */ }
+};
+
static struct driver_d atmel_serial_driver = {
.name = "atmel_usart",
.probe = atmel_serial_probe,
+ .of_compatible = DRV_OF_COMPAT(atmel_serial_dt_ids),
};
console_platform_driver(atmel_serial_driver);
diff --git a/drivers/spi/atmel_spi.c b/drivers/spi/atmel_spi.c
index ef57867759..446e1d9932 100644
--- a/drivers/spi/atmel_spi.c
+++ b/drivers/spi/atmel_spi.c
@@ -29,6 +29,7 @@
#include <clock.h>
#include <xfuncs.h>
#include <gpio.h>
+#include <of_gpio.h>
#include <io.h>
#include <spi/spi.h>
#include <mach/io.h>
@@ -400,10 +401,11 @@ static int atmel_spi_probe(struct device_d *dev)
int ret = 0;
int i;
struct spi_master *master;
+ struct device_node *node = dev->device_node;
struct atmel_spi *as;
struct at91_spi_platform_data *pdata = dev->platform_data;
- if (!pdata) {
+ if (!IS_ENABLED(CONFIG_OFDEVICE) && !pdata) {
dev_err(dev, "missing platform data\n");
return -EINVAL;
}
@@ -414,6 +416,23 @@ static int atmel_spi_probe(struct device_d *dev)
master->dev = dev;
master->bus_num = dev->id;
+ if (pdata) {
+ master->num_chipselect = pdata->num_chipselect;
+ as->cs_pins = pdata->chipselect;
+ } else {
+ master->num_chipselect = of_gpio_named_count(node, "cs-gpios");
+ as->cs_pins = xzalloc(sizeof(u32) * master->num_chipselect);
+
+ for (i = 0; i < master->num_chipselect; i++) {
+ as->cs_pins[i] = of_get_named_gpio(node, "cs-gpios", i);
+
+ if (!gpio_is_valid(as->cs_pins[i]))
+ break;
+ }
+
+ master->num_chipselect = i;
+ }
+
as->clk = clk_get(dev, "spi_clk");
if (IS_ERR(as->clk)) {
dev_err(dev, "no spi_clk\n");
@@ -423,8 +442,6 @@ static int atmel_spi_probe(struct device_d *dev)
master->setup = atmel_spi_setup;
master->transfer = atmel_spi_transfer;
- master->num_chipselect = pdata->num_chipselect;
- as->cs_pins = pdata->chipselect;
iores = dev_request_mem_resource(dev, 0);
if (IS_ERR(iores))
return PTR_ERR(iores);
@@ -465,8 +482,14 @@ out_free:
return ret;
}
+const static __maybe_unused const struct of_device_id atmel_spi_dt_ids[] = {
+ { .compatible = "atmel,at91rm9200-spi" },
+ { /* sentinel */ }
+};
+
static struct driver_d atmel_spi_driver = {
.name = "atmel_spi",
.probe = atmel_spi_probe,
+ .of_compatible = DRV_OF_COMPAT(atmel_spi_dt_ids),
};
device_platform_driver(atmel_spi_driver);
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index eb279ae8df..4292371f09 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -30,6 +30,17 @@ config USB_GADGET_DRIVER_PXA27X
default y
select USB_GADGET_DUALSPEED
+config USB_GADGET_AUTOSTART
+ bool
+ default y
+ select ENVIRONMENT_VARIABLES
+ select FILE_LIST
+ prompt "Automatically start usbgadget on boot"
+ help
+ Enabling this option allows to automatically start a fastboot
+ gadget during boot. This behaviour is controlled with the
+ global.usbgadget.fastboot_function variable.
+
comment "USB Gadget drivers"
config USB_GADGET_DFU
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 9ef594575b..e74cf02664 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -1,5 +1,6 @@
obj-$(CONFIG_USB_GADGET) += composite.o config.o usbstring.o epautoconf.o udc-core.o functions.o config.o multi.o
+obj-$(CONFIG_USB_GADGET_AUTOSTART) += autostart.o
obj-$(CONFIG_USB_GADGET_SERIAL) += u_serial.o serial.o f_serial.o f_acm.o
obj-$(CONFIG_USB_GADGET_DFU) += dfu.o
obj-$(CONFIG_USB_GADGET_FASTBOOT) += f_fastboot.o
diff --git a/drivers/usb/gadget/autostart.c b/drivers/usb/gadget/autostart.c
new file mode 100644
index 0000000000..4ad1dd6be1
--- /dev/null
+++ b/drivers/usb/gadget/autostart.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2017 Oleksij Rempel <o.rempel@pengutronix.de>, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <command.h>
+#include <errno.h>
+#include <environment.h>
+#include <malloc.h>
+#include <getopt.h>
+#include <fs.h>
+#include <xfuncs.h>
+#include <usb/usbserial.h>
+#include <usb/dfu.h>
+#include <usb/gadget-multi.h>
+#include <globalvar.h>
+#include <magicvar.h>
+
+static int autostart;
+static int acm;
+static char *fastboot_function;
+
+static int usbgadget_autostart(void)
+{
+ struct f_multi_opts opts = {};
+
+ if (!autostart)
+ return 0;
+
+ setenv("otg.mode", "peripheral");
+
+ if (fastboot_function)
+ opts.fastboot_opts.files = file_list_parse(fastboot_function);
+
+ opts.create_acm = acm;
+
+ return usb_multi_register(&opts);
+}
+postenvironment_initcall(usbgadget_autostart);
+
+static int usbgadget_globalvars_init(void)
+{
+
+ globalvar_add_simple_bool("usbgadget.autostart", &autostart);
+ globalvar_add_simple_bool("usbgadget.acm", &acm);
+ globalvar_add_simple_string("usbgadget.fastboot_function",
+ &fastboot_function);
+
+ return 0;
+}
+device_initcall(usbgadget_globalvars_init);
+
+BAREBOX_MAGICVAR_NAMED(global_usbgadget_autostart,
+ global.usbgadget.autostart,
+ "usbgadget: Automatically start usbgadget on boot");
+BAREBOX_MAGICVAR_NAMED(global_usbgadget_acm,
+ global.usbgadget.acm,
+ "usbgadget: Create CDC ACM function");
+BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_function,
+ global.usbgadget.fastboot_function,
+ "usbgadget: Create Android Fastboot function");
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 54eaf468b7..db44052525 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -10,20 +10,17 @@ config USB_EHCI_OMAP
config USB_EHCI_ATMEL
depends on ARCH_AT91
depends on USB_EHCI
+ select USB_OHCI_AT91
bool "Atmel EHCI driver"
config USB_OHCI
bool "OHCI driver"
depends on !MMU
-if USB_OHCI
-
config USB_OHCI_AT91
depends on ARCH_AT91
bool "AT91 OHCI driver"
-endif
-
config USB_XHCI
bool "xHCI driver"
depends on HAS_DMA
diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c
index f075b5080c..1132879c9b 100644
--- a/drivers/usb/host/ehci-atmel.c
+++ b/drivers/usb/host/ehci-atmel.c
@@ -65,6 +65,9 @@ static int atmel_ehci_probe(struct device_d *dev)
struct resource *iores;
struct ehci_data data;
struct atmel_ehci_priv *atehci;
+ const char *uclk_name;
+
+ uclk_name = (dev->device_node) ? "usb_clk" : "uhpck";
atehci = xzalloc(sizeof(*atehci));
atehci->dev = dev;
@@ -76,7 +79,7 @@ static int atmel_ehci_probe(struct device_d *dev)
return -ENOENT;
}
- atehci->uclk = clk_get(dev, "uhpck");
+ atehci->uclk = clk_get(dev, uclk_name);
if (IS_ERR(atehci->iclk)) {
dev_err(dev, "Error getting function clock\n");
return -ENOENT;
@@ -107,9 +110,15 @@ static void atmel_ehci_remove(struct device_d *dev)
atmel_stop_clock(dev->priv);
}
+static const struct of_device_id atmel_ehci_dt_ids[] = {
+ { .compatible = "atmel,at91sam9g45-ehci" },
+ { /* sentinel */ }
+};
+
static struct driver_d atmel_ehci_driver = {
.name = "atmel-ehci",
.probe = atmel_ehci_probe,
.remove = atmel_ehci_remove,
+ .of_compatible = DRV_OF_COMPAT(atmel_ehci_dt_ids),
};
device_platform_driver(atmel_ehci_driver);
diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c
index 5f745264b7..1013ba39c5 100644
--- a/drivers/usb/host/ohci-at91.c
+++ b/drivers/usb/host/ohci-at91.c
@@ -23,10 +23,18 @@
#include <usb/usb.h>
#include <usb/usb_defs.h>
#include <errno.h>
+#include <gpio.h>
+#include <of_gpio.h>
#include <io.h>
+#include <mach/board.h>
+
#include "ohci.h"
+#define at91_for_each_port(index) \
+ for ((index) = 0; (index) < AT91_MAX_USBH_PORTS; (index)++)
+
+
struct ohci_at91_priv {
struct device_d *dev;
struct clk *iclk;
@@ -59,6 +67,59 @@ static void at91_stop_clock(struct ohci_at91_priv *ohci_at91)
clk_disable(ohci_at91->iclk);
}
+static int at91_ohci_probe_dt(struct device_d *dev)
+{
+ u32 ports;
+ int i, ret, gpio;
+ enum of_gpio_flags flags;
+ struct at91_usbh_data *pdata;
+ struct device_node *np = dev->device_node;
+
+ pdata = xzalloc(sizeof(*pdata));
+ dev->platform_data = pdata;
+
+ if (!of_property_read_u32(np, "num-ports", &ports)) {
+ pdata->ports = ports;
+ } else {
+ dev_err(dev, "Failed to read 'num-ports' property\n");
+ return -EINVAL;
+ }
+
+ at91_for_each_port(i) {
+ /*
+ * do not configure PIO if not in relation with
+ * real USB port on board
+ */
+ if (i >= pdata->ports) {
+ pdata->vbus_pin[i] = -EINVAL;
+ continue;
+ }
+
+ gpio = of_get_named_gpio_flags(np, "atmel,vbus-gpio", i,
+ &flags);
+ pdata->vbus_pin[i] = gpio;
+ if (!gpio_is_valid(gpio))
+ continue;
+ pdata->vbus_pin_active_low[i] = flags & OF_GPIO_ACTIVE_LOW;
+
+ ret = gpio_request(gpio, "ohci_vbus");
+ if (ret) {
+ dev_err(dev, "can't request vbus gpio %d\n", gpio);
+ continue;
+ }
+ ret = gpio_direction_output(gpio,
+ !pdata->vbus_pin_active_low[i]);
+ if (ret) {
+ dev_err(dev, "can't put vbus gpio %d as output %d\n",
+ gpio, !pdata->vbus_pin_active_low[i]);
+ gpio_free(gpio);
+ continue;
+ }
+ }
+
+ return 0;
+}
+
static int at91_ohci_probe(struct device_d *dev)
{
int ret;
@@ -68,6 +129,12 @@ static int at91_ohci_probe(struct device_d *dev)
dev->priv = ohci_at91;
ohci_at91->dev = dev;
+ if (dev->device_node) {
+ ret = at91_ohci_probe_dt(dev);
+ if (ret < 0)
+ return ret;
+ }
+
io = dev_get_resource(dev, IORESOURCE_MEM, 0);
if (IS_ERR(io)) {
dev_err(dev, "Failed to get IORESOURCE_MEM\n");
@@ -75,13 +142,13 @@ static int at91_ohci_probe(struct device_d *dev)
}
ohci_at91->regs = IOMEM(io->start);
- ohci_at91->iclk = clk_get(NULL, "ohci_clk");
+ ohci_at91->iclk = clk_get(dev, "ohci_clk");
if (IS_ERR(ohci_at91->iclk)) {
dev_err(dev, "Failed to get 'ohci_clk'\n");
return PTR_ERR(ohci_at91->iclk);
}
- ohci_at91->fclk = clk_get(NULL, "uhpck");
+ ohci_at91->fclk = clk_get(dev, "uhpck");
if (IS_ERR(ohci_at91->fclk)) {
dev_err(dev, "Failed to get 'uhpck'\n");
return PTR_ERR(ohci_at91->fclk);
@@ -107,6 +174,7 @@ static int at91_ohci_probe(struct device_d *dev)
static void at91_ohci_remove(struct device_d *dev)
{
+ struct at91_usbh_data *pdata = dev->platform_data;
struct ohci_at91_priv *ohci_at91 = dev->priv;
/*
@@ -118,11 +186,32 @@ static void at91_ohci_remove(struct device_d *dev)
* Stop the USB clocks.
*/
at91_stop_clock(ohci_at91);
+
+ if (pdata) {
+ bool active_low;
+ int i, gpio;
+
+ at91_for_each_port(i) {
+ gpio = pdata->vbus_pin[i];
+ active_low = pdata->vbus_pin_active_low[i];
+
+ if (gpio_is_valid(gpio)) {
+ gpio_set_value(gpio, active_low);
+ gpio_free(gpio);
+ }
+ }
+ }
}
+static const struct of_device_id at91_ohci_dt_ids[] = {
+ { .compatible = "atmel,at91rm9200-ohci" },
+ { /* sentinel */ }
+};
+
static struct driver_d at91_ohci_driver = {
.name = "at91_ohci",
.probe = at91_ohci_probe,
.remove = at91_ohci_remove,
+ .of_compatible = DRV_OF_COMPAT(at91_ohci_dt_ids),
};
device_platform_driver(at91_ohci_driver);
diff --git a/drivers/video/edid.c b/drivers/video/edid.c
index 258526433e..bee4594118 100644
--- a/drivers/video/edid.c
+++ b/drivers/video/edid.c
@@ -387,7 +387,7 @@ static void fb_timings_vfreq(struct __fb_timings *timings)
* REQUIRES:
* A valid info->monspecs, otherwise 'safe numbers' will be used.
*/
-int fb_get_mode(int flags, u32 val, struct fb_videomode *var)
+static int fb_get_mode(int flags, u32 val, struct fb_videomode *var)
{
struct __fb_timings *timings;
u32 interlace = 1, dscan = 1;
diff --git a/drivers/video/imx-ipu-v3/ipufb.c b/drivers/video/imx-ipu-v3/ipufb.c
index 343f9e5578..9597eda0d0 100644
--- a/drivers/video/imx-ipu-v3/ipufb.c
+++ b/drivers/video/imx-ipu-v3/ipufb.c
@@ -333,10 +333,8 @@ static int ipufb_probe(struct device_d *dev)
}
ret = vpl_ioctl(&fbi->vpl, 2 + fbi->dino, VPL_GET_VIDEOMODES, &info->modes);
- if (ret) {
- dev_err(fbi->dev, "failed to get modes: %s\n", strerror(-ret));
- return ret;
- }
+ if (ret)
+ dev_dbg(fbi->dev, "failed to get modes: %s\n", strerror(-ret));
ret = register_framebuffer(info);
if (ret < 0) {
diff --git a/drivers/video/simplefb.c b/drivers/video/simplefb.c
index 357262988e..a2c59de364 100644
--- a/drivers/video/simplefb.c
+++ b/drivers/video/simplefb.c
@@ -89,9 +89,6 @@ static const struct simplefb_mode *simplefb_find_mode(const struct fb_info *fbi)
static int simplefb_create_node(struct device_node *root,
const struct fb_info *fbi, const char *format)
{
- const char *compat = "simple-framebuffer";
- const char *disabled = "disabled";
- const char *okay = "okay";
struct device_node *node;
u32 cells[2];
int ret;
@@ -100,12 +97,11 @@ static int simplefb_create_node(struct device_node *root,
if (!node)
return -ENOMEM;
- ret = of_set_property(node, "status", disabled,
- strlen(disabled) + 1, 1);
+ ret = of_property_write_string(node, "status", "disabled");
if (ret < 0)
return ret;
- ret = of_set_property(node, "compatible", compat, strlen(compat) + 1, 1);
+ ret = of_property_write_string(node, "compatible", "simple-framebuffer");
if (ret)
return ret;
@@ -130,14 +126,14 @@ static int simplefb_create_node(struct device_node *root,
if (ret < 0)
return ret;
- ret = of_set_property(node, "format", format, strlen(format) + 1, 1);
+ ret = of_property_write_string(node, "format", format);
if (ret < 0)
return ret;
of_add_reserve_entry((u32)fbi->screen_base,
(u32)fbi->screen_base + fbi->screen_size);
- return of_set_property(node, "status", okay, strlen(okay) + 1, 1);
+ return of_property_write_string(node, "status", "okay");
}
static int simplefb_of_fixup(struct device_node *root, void *ctx)
diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c
index 946e9d3340..916027ea87 100644
--- a/drivers/w1/masters/w1-gpio.c
+++ b/drivers/w1/masters/w1-gpio.c
@@ -16,6 +16,7 @@
#include <driver.h>
#include <linux/w1-gpio.h>
#include <gpio.h>
+#include <of_gpio.h>
#include "../w1.h"
@@ -43,12 +44,58 @@ static u8 w1_gpio_read_bit(struct w1_bus *bus)
return gpio_get_value(pdata->pin) ? 1 : 0;
}
+static int w1_gpio_probe_dt(struct device_d *dev)
+{
+ struct w1_gpio_platform_data *pdata;
+ struct device_node *np = dev->device_node;
+ int gpio;
+
+ if (dev->platform_data)
+ return 0;
+
+ pdata = xzalloc(sizeof(*pdata));
+
+ if (of_get_property(np, "linux,open-drain", NULL))
+ pdata->is_open_drain = 1;
+
+ gpio = of_get_gpio(np, 0);
+ if (!gpio_is_valid(gpio)) {
+ if (gpio != -EPROBE_DEFER)
+ dev_err(dev,
+ "Failed to parse gpio property for data pin (%d)\n",
+ gpio);
+
+ goto free_pdata;
+ }
+ pdata->pin = gpio;
+
+ gpio = of_get_gpio(np, 1);
+ if (gpio == -EPROBE_DEFER)
+ goto free_pdata;
+
+ /* ignore other errors as the pullup gpio is optional */
+ pdata->ext_pullup_enable_pin = gpio;
+
+ dev->platform_data = pdata;
+ return 0;
+
+free_pdata:
+ free(pdata);
+ return gpio;
+}
+
static int __init w1_gpio_probe(struct device_d *dev)
{
struct w1_bus *master;
struct w1_gpio_platform_data *pdata;
int err;
+ if (IS_ENABLED(CONFIG_OFDEVICE)) {
+ err = w1_gpio_probe_dt(dev);
+ if (err < 0)
+ return err;
+ }
+
pdata = dev->platform_data;
if (!pdata)
@@ -104,8 +151,14 @@ static int __init w1_gpio_probe(struct device_d *dev)
return err;
}
+static __maybe_unused const struct of_device_id w1_gpio_dt_ids[] = {
+ { .compatible = "w1-gpio" },
+ {}
+};
+
static struct driver_d w1_gpio_driver = {
.name = "w1-gpio",
.probe = w1_gpio_probe,
+ .of_compatible = DRV_OF_COMPAT(w1_gpio_dt_ids),
};
device_platform_driver(w1_gpio_driver);
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 63fb1a8c57..83b6528a5f 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -46,4 +46,10 @@ config WATCHDOG_OMAP
help
Add support for watchdog on the TI OMAP SoC.
+config WATCHDOG_ORION
+ bool "Watchdog for Armada XP"
+ depends on ARCH_ARMADA_XP
+ help
+ Add support for watchdog on the Marvall Armada XP
+
endif
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 5fca4c368c..a3b26675ce 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_WATCHDOG_MXS28) += im28wd.o
obj-$(CONFIG_WATCHDOG_DW) += dw_wdt.o
obj-$(CONFIG_WATCHDOG_JZ4740) += jz4740.o
obj-$(CONFIG_WATCHDOG_IMX_RESET_SOURCE) += imxwd.o
+obj-$(CONFIG_WATCHDOG_ORION) += orion_wdt.o
diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c
new file mode 100644
index 0000000000..2802033f71
--- /dev/null
+++ b/drivers/watchdog/orion_wdt.c
@@ -0,0 +1,123 @@
+/*
+ * Watchdog driver for Marvell Armada XP.
+ *
+ * Copyright (C) 2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <common.h>
+#include <errno.h>
+#include <init.h>
+#include <io.h>
+#include <malloc.h>
+#include <of.h>
+#include <watchdog.h>
+
+#define CLKRATE 25000000
+
+/* registers relative to timer_base (i.e. first reg property) */
+#define TIMER_CTRL 0x00
+#define TIMER_CTRL_WD_TIMER_25MHZ_EN BIT(10)
+#define TIMER_CTRL_WD_TIMER_EN BIT(8)
+
+#define TIMER_STATUS 0x04
+#define TIMER_STATUS_WD_EXPIRED BIT(31)
+
+#define TIMER_WD_TIMER 0x34
+
+/* registers relative to rstout_base (i.e. second reg property) */
+#define WD_RSTOUTn_MASK 0x00
+#define WD_RSTOUTn_MASK_GLOBAL_WD BIT(8)
+
+struct orion_wdt_ddata {
+ struct watchdog wd;
+ void __iomem *timer_base;
+ void __iomem *rstout_base;
+};
+
+static int armada_xp_set_timeout(struct watchdog *wd, unsigned timeout)
+{
+ struct orion_wdt_ddata *ddata =
+ container_of(wd, struct orion_wdt_ddata, wd);
+ u32 ctrl;
+
+ if (0xffffffff / CLKRATE < timeout)
+ return -EINVAL;
+
+ ctrl = readl(ddata->timer_base + TIMER_CTRL);
+
+ if (timeout == 0) {
+ /* disable timer */
+ ctrl &= ~TIMER_CTRL_WD_TIMER_EN;
+ writel(ctrl, ddata->timer_base + TIMER_CTRL);
+
+ return 0;
+ }
+
+ /* setup duration */
+ writel(CLKRATE * timeout, ddata->timer_base + TIMER_WD_TIMER);
+
+ /* clear expiration status */
+ writel(readl(ddata->timer_base + TIMER_STATUS) & ~TIMER_STATUS_WD_EXPIRED,
+ ddata->timer_base + TIMER_STATUS);
+
+ /* assert reset on expiration */
+ writel(WD_RSTOUTn_MASK_GLOBAL_WD, ddata->rstout_base + WD_RSTOUTn_MASK);
+
+ /* enable */
+ ctrl |= TIMER_CTRL_WD_TIMER_25MHZ_EN | TIMER_CTRL_WD_TIMER_EN;
+ writel(ctrl, ddata->timer_base + TIMER_CTRL);
+
+ return 0;
+}
+
+static int orion_wdt_probe(struct device_d *dev)
+{
+ struct orion_wdt_ddata* ddata;
+ struct resource *res_timer, *res_rstout;
+
+ ddata = xzalloc(sizeof(*ddata));
+
+ ddata->wd.set_timeout = armada_xp_set_timeout;
+ ddata->wd.name = "orion_wdt";
+ ddata->wd.dev = dev;
+
+ res_timer = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res_timer)) {
+ dev_err(dev, "could not get timer memory region\n");
+ return PTR_ERR(res_timer);
+ }
+ ddata->timer_base = IOMEM(res_timer->start);
+
+ res_rstout = dev_request_mem_resource(dev, 1);
+ if (IS_ERR(res_rstout)) {
+ dev_err(dev, "could not get rstout memory region\n");
+ release_region(res_timer);
+
+ return PTR_ERR(res_rstout);
+ }
+ ddata->rstout_base = IOMEM(res_rstout->start);
+
+ return watchdog_register(&ddata->wd);
+}
+
+static const struct of_device_id orion_wdt_of_match[] = {
+ {
+ .compatible = "marvell,armada-xp-wdt",
+ }, { /* sentinel */ }
+};
+
+static struct driver_d orion_wdt_driver = {
+ .probe = orion_wdt_probe,
+ .name = "orion_wdt",
+ .of_compatible = DRV_OF_COMPAT(orion_wdt_of_match),
+};
+device_platform_driver(orion_wdt_driver);