diff options
author | Arnd Bergmann <arnd@arndb.de> | 2021-08-04 16:03:40 +0200 |
---|---|---|
committer | Arnd Bergmann <arnd@arndb.de> | 2021-08-04 16:03:41 +0200 |
commit | 775dea4deec679c24b525b258c341a45ff1de9ea (patch) | |
tree | ba2b28c24a3efba51fa5e428f249841b8a4158cb | |
parent | b6e952c35267b8da9a402f2f0c9ea23d5d929774 (diff) | |
parent | 47adef20e67d657696c953f4b8023017c6005c1b (diff) |
Merge tag 'ixp4xx-drivers-arm-soc-v5.15-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-nomadik into arm/drivers
IXP4xx driver updates for modernizing the IXP4xx platforms,
taregeted for v5.15:
- Add DT bindings to the expansion bus and PATA libata driver.
- Add a new expansion bus driver.
- Rewrite the watchdog driver to use the watchdog core and
spawn from the timer (clocksource) driver.
- Refactor the PATA/libata driver to probe from the device
tree and use the expansion bus driver to manipulate chip
select timings directly.
* tag 'ixp4xx-drivers-arm-soc-v5.15-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-nomadik:
pata: ixp4xx: Rewrite to use device tree
pata: ixp4xx: Add DT bindings
pata: ixp4xx: Refer to cmd and ctl rather than csN
pata: ixp4xx: Use IS_ENABLED() to determine endianness
pata: ixp4xx: Use local dev variable
watchdog: ixp4xx: Rewrite driver to use core
bus: ixp4xx: Add a driver for IXP4xx expansion bus
bus: ixp4xx: Add DT bindings for the IXP4xx expansion bus
Link: https://lore.kernel.org/r/CACRpkdZaCosXsgp02nuUbd_nEvdxm5-z0+d0oSA97UTWQ0RQQg@mail.gmail.com
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
-rw-r--r-- | Documentation/devicetree/bindings/ata/intel,ixp4xx-compact-flash.yaml | 61 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/bus/intel,ixp4xx-expansion-bus-controller.yaml | 168 | ||||
-rw-r--r-- | MAINTAINERS | 2 | ||||
-rw-r--r-- | drivers/ata/pata_ixp4xx_cf.c | 264 | ||||
-rw-r--r-- | drivers/bus/Kconfig | 11 | ||||
-rw-r--r-- | drivers/bus/Makefile | 1 | ||||
-rw-r--r-- | drivers/bus/intel-ixp4xx-eb.c | 429 | ||||
-rw-r--r-- | drivers/clocksource/timer-ixp4xx.c | 48 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 1 | ||||
-rw-r--r-- | drivers/watchdog/ixp4xx_wdt.c | 283 | ||||
-rw-r--r-- | include/linux/platform_data/pata_ixp4xx_cf.h | 4 |
11 files changed, 1014 insertions, 258 deletions
diff --git a/Documentation/devicetree/bindings/ata/intel,ixp4xx-compact-flash.yaml b/Documentation/devicetree/bindings/ata/intel,ixp4xx-compact-flash.yaml new file mode 100644 index 000000000000..52e18600ecff --- /dev/null +++ b/Documentation/devicetree/bindings/ata/intel,ixp4xx-compact-flash.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ata/intel,ixp4xx-compact-flash.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Intel IXP4xx CompactFlash Card Controller + +maintainers: + - Linus Walleij <linus.walleij@linaro.org> + +description: | + The IXP4xx network processors have a CompactFlash interface that presents + a CompactFlash card to the system as a true IDE (parallel ATA) device. The + device is always connected to the expansion bus of the IXP4xx SoCs using one + or two chip select areas and address translating logic on the board. The + node must be placed inside a chip select node on the IXP4xx expansion bus. + +properties: + compatible: + const: intel,ixp4xx-compact-flash + + reg: + items: + - description: Command interface registers + - description: Control interface registers + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +allOf: + - $ref: pata-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + + bus@c4000000 { + compatible = "intel,ixp43x-expansion-bus-controller", "syscon"; + reg = <0xc4000000 0x1000>; + native-endian; + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0x0 0x50000000 0x01000000>, <1 0x0 0x51000000 0x01000000>; + dma-ranges = <0 0x0 0x50000000 0x01000000>, <1 0x0 0x51000000 0x01000000>; + ide@1,0 { + compatible = "intel,ixp4xx-compact-flash"; + reg = <1 0x00000000 0x1000>, <1 0x00040000 0x1000>; + interrupt-parent = <&gpio0>; + interrupts = <12 IRQ_TYPE_EDGE_RISING>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/bus/intel,ixp4xx-expansion-bus-controller.yaml b/Documentation/devicetree/bindings/bus/intel,ixp4xx-expansion-bus-controller.yaml new file mode 100644 index 000000000000..5fb4e7bfa4da --- /dev/null +++ b/Documentation/devicetree/bindings/bus/intel,ixp4xx-expansion-bus-controller.yaml @@ -0,0 +1,168 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/bus/intel,ixp4xx-expansion-bus-controller.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Intel IXP4xx Expansion Bus Controller + +description: | + The IXP4xx expansion bus controller handles access to devices on the + memory-mapped expansion bus on the Intel IXP4xx family of system on chips, + including IXP42x, IXP43x, IXP45x and IXP46x. + +maintainers: + - Linus Walleij <linus.walleij@linaro.org> + +properties: + $nodename: + pattern: '^bus@[0-9a-f]+$' + + compatible: + items: + - enum: + - intel,ixp42x-expansion-bus-controller + - intel,ixp43x-expansion-bus-controller + - intel,ixp45x-expansion-bus-controller + - intel,ixp46x-expansion-bus-controller + - const: syscon + + reg: + description: Control registers for the expansion bus, these are not + inside the memory range handled by the expansion bus. + maxItems: 1 + + native-endian: + $ref: /schemas/types.yaml#/definitions/flag + description: The IXP4xx has a peculiar MMIO access scheme, as it changes + the access pattern for words (swizzling) on the bus depending on whether + the SoC is running in big-endian or little-endian mode. Thus the + registers must always be accessed using native endianness. + + "#address-cells": + description: | + The first cell is the chip select number. + The second cell is the address offset within the bank. + const: 2 + + "#size-cells": + const: 1 + + ranges: true + dma-ranges: true + +patternProperties: + "^.*@[0-7],[0-9a-f]+$": + description: Devices attached to chip selects are represented as + subnodes. + type: object + + properties: + intel,ixp4xx-eb-t1: + description: Address timing, extend address phase with n cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 3 + + intel,ixp4xx-eb-t2: + description: Setup chip select timing, extend setup phase with n cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 3 + + intel,ixp4xx-eb-t3: + description: Strobe timing, extend strobe phase with n cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 15 + + intel,ixp4xx-eb-t4: + description: Hold timing, extend hold phase with n cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 3 + + intel,ixp4xx-eb-t5: + description: Recovery timing, extend recovery phase with n cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 15 + + intel,ixp4xx-eb-cycle-type: + description: The type of cycles to use on the expansion bus for this + chip select. 0 = Intel cycles, 1 = Motorola cycles, 2 = HPI cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + + intel,ixp4xx-eb-byte-access-on-halfword: + description: Allow byte read access on half word devices. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + + intel,ixp4xx-eb-hpi-hrdy-pol-high: + description: Set HPI HRDY polarity to active high when using HPI. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + + intel,ixp4xx-eb-mux-address-and-data: + description: Multiplex address and data on the data bus. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + + intel,ixp4xx-eb-ahb-split-transfers: + description: Enable AHB split transfers. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + + intel,ixp4xx-eb-write-enable: + description: Enable write cycles. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + + intel,ixp4xx-eb-byte-access: + description: Expansion bus uses only 8 bits. The default is to use + 16 bits. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + +required: + - compatible + - reg + - native-endian + - "#address-cells" + - "#size-cells" + - ranges + - dma-ranges + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + bus@50000000 { + compatible = "intel,ixp42x-expansion-bus-controller", "syscon"; + reg = <0xc4000000 0x28>; + native-endian; + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0x0 0x50000000 0x01000000>, + <1 0x0 0x51000000 0x01000000>; + dma-ranges = <0 0x0 0x50000000 0x01000000>, + <1 0x0 0x51000000 0x01000000>; + flash@0,0 { + compatible = "intel,ixp4xx-flash", "cfi-flash"; + bank-width = <2>; + reg = <0 0x00000000 0x1000000>; + intel,ixp4xx-eb-t3 = <3>; + intel,ixp4xx-eb-cycle-type = <0>; + intel,ixp4xx-eb-byte-access-on-halfword = <1>; + intel,ixp4xx-eb-write-enable = <1>; + intel,ixp4xx-eb-byte-access = <0>; + }; + serial@1,0 { + compatible = "exar,xr16l2551", "ns8250"; + reg = <1 0x00000000 0x10>; + interrupt-parent = <&gpio0>; + interrupts = <4 IRQ_TYPE_LEVEL_LOW>; + clock-frequency = <1843200>; + intel,ixp4xx-eb-t3 = <3>; + intel,ixp4xx-eb-cycle-type = <1>; + intel,ixp4xx-eb-write-enable = <1>; + intel,ixp4xx-eb-byte-access = <1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c9467d2839f5..3a7fce893d7a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2010,10 +2010,12 @@ M: Krzysztof Halasa <khalasa@piap.pl> L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/arm/intel-ixp4xx.yaml +F: Documentation/devicetree/bindings/bus/intel,ixp4xx-expansion-bus-controller.yaml F: Documentation/devicetree/bindings/gpio/intel,ixp4xx-gpio.txt F: Documentation/devicetree/bindings/interrupt-controller/intel,ixp4xx-interrupt.yaml F: Documentation/devicetree/bindings/timer/intel,ixp4xx-timer.yaml F: arch/arm/mach-ixp4xx/ +F: drivers/bus/intel-ixp4xx-eb.c F: drivers/clocksource/timer-ixp4xx.c F: drivers/crypto/ixp4xx_crypto.c F: drivers/gpio/gpio-ixp4xx.c diff --git a/drivers/ata/pata_ixp4xx_cf.c b/drivers/ata/pata_ixp4xx_cf.c index 5881d64af943..99c63087c8ae 100644 --- a/drivers/ata/pata_ixp4xx_cf.c +++ b/drivers/ata/pata_ixp4xx_cf.c @@ -13,45 +13,134 @@ */ #include <linux/kernel.h> +#include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/libata.h> #include <linux/irq.h> #include <linux/platform_device.h> -#include <linux/platform_data/pata_ixp4xx_cf.h> +#include <linux/regmap.h> #include <scsi/scsi_host.h> #define DRV_NAME "pata_ixp4xx_cf" -#define DRV_VERSION "0.2" +#define DRV_VERSION "1.0" -static int ixp4xx_set_mode(struct ata_link *link, struct ata_device **error) +struct ixp4xx_pata { + struct ata_host *host; + struct regmap *rmap; + u32 cmd_csreg; + void __iomem *cmd; + void __iomem *ctl; +}; + +#define IXP4XX_EXP_TIMING_STRIDE 0x04 +/* The timings for the chipselect is in bits 29..16 */ +#define IXP4XX_EXP_T1_T5_MASK GENMASK(29, 16) +#define IXP4XX_EXP_PIO_0_8 0x0a470000 +#define IXP4XX_EXP_PIO_1_8 0x06430000 +#define IXP4XX_EXP_PIO_2_8 0x02410000 +#define IXP4XX_EXP_PIO_3_8 0x00820000 +#define IXP4XX_EXP_PIO_4_8 0x00400000 +#define IXP4XX_EXP_PIO_0_16 0x29640000 +#define IXP4XX_EXP_PIO_1_16 0x05030000 +#define IXP4XX_EXP_PIO_2_16 0x00b20000 +#define IXP4XX_EXP_PIO_3_16 0x00820000 +#define IXP4XX_EXP_PIO_4_16 0x00400000 +#define IXP4XX_EXP_BW_MASK (BIT(6)|BIT(0)) +#define IXP4XX_EXP_BYTE_RD16 BIT(6) /* Byte reads on half-word devices */ +#define IXP4XX_EXP_BYTE_EN BIT(0) /* Use 8bit data bus if set */ + +static void ixp4xx_set_8bit_timing(struct ixp4xx_pata *ixpp, u8 pio_mode) +{ + switch (pio_mode) { + case XFER_PIO_0: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_0_8); + break; + case XFER_PIO_1: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_1_8); + break; + case XFER_PIO_2: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_2_8); + break; + case XFER_PIO_3: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_3_8); + break; + case XFER_PIO_4: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_4_8); + break; + default: + break; + } + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_BW_MASK, IXP4XX_EXP_BYTE_RD16|IXP4XX_EXP_BYTE_EN); +} + +static void ixp4xx_set_16bit_timing(struct ixp4xx_pata *ixpp, u8 pio_mode) { - struct ata_device *dev; - - ata_for_each_dev(dev, link, ENABLED) { - ata_dev_info(dev, "configured for PIO0\n"); - dev->pio_mode = XFER_PIO_0; - dev->xfer_mode = XFER_PIO_0; - dev->xfer_shift = ATA_SHIFT_PIO; - dev->flags |= ATA_DFLAG_PIO; + switch (pio_mode){ + case XFER_PIO_0: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_0_16); + break; + case XFER_PIO_1: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_1_16); + break; + case XFER_PIO_2: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_2_16); + break; + case XFER_PIO_3: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_3_16); + break; + case XFER_PIO_4: + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_T1_T5_MASK, IXP4XX_EXP_PIO_4_16); + break; + default: + break; } - return 0; + regmap_update_bits(ixpp->rmap, ixpp->cmd_csreg, + IXP4XX_EXP_BW_MASK, IXP4XX_EXP_BYTE_RD16); +} + +/* This sets up the timing on the chipselect CMD accordingly */ +static void ixp4xx_set_piomode(struct ata_port *ap, struct ata_device *adev) +{ + struct ixp4xx_pata *ixpp = ap->host->private_data; + + ata_dev_printk(adev, KERN_INFO, "configured for PIO%d 8bit\n", + adev->pio_mode - XFER_PIO_0); + ixp4xx_set_8bit_timing(ixpp, adev->pio_mode); } + static unsigned int ixp4xx_mmio_data_xfer(struct ata_queued_cmd *qc, - unsigned char *buf, unsigned int buflen, int rw) + unsigned char *buf, unsigned int buflen, int rw) { unsigned int i; unsigned int words = buflen >> 1; u16 *buf16 = (u16 *) buf; + struct ata_device *adev = qc->dev; struct ata_port *ap = qc->dev->link->ap; void __iomem *mmio = ap->ioaddr.data_addr; - struct ixp4xx_pata_data *data = dev_get_platdata(ap->host->dev); + struct ixp4xx_pata *ixpp = ap->host->private_data; + unsigned long flags; + + ata_dev_printk(adev, KERN_DEBUG, "%s %d bytes\n", (rw == READ) ? "READ" : "WRITE", + buflen); + spin_lock_irqsave(ap->lock, flags); /* set the expansion bus in 16bit mode and restore * 8 bit mode after the transaction. */ - *data->cs0_cfg &= ~(0x01); - udelay(100); + ixp4xx_set_16bit_timing(ixpp, adev->pio_mode); + udelay(5); /* Transfer multiple of 2 bytes */ if (rw == READ) @@ -76,8 +165,10 @@ static unsigned int ixp4xx_mmio_data_xfer(struct ata_queued_cmd *qc, words++; } - udelay(100); - *data->cs0_cfg |= 0x01; + ixp4xx_set_8bit_timing(ixpp, adev->pio_mode); + udelay(5); + + spin_unlock_irqrestore(ap->lock, flags); return words << 1; } @@ -90,79 +181,98 @@ static struct ata_port_operations ixp4xx_port_ops = { .inherits = &ata_sff_port_ops, .sff_data_xfer = ixp4xx_mmio_data_xfer, .cable_detect = ata_cable_40wire, - .set_mode = ixp4xx_set_mode, + .set_piomode = ixp4xx_set_piomode, +}; + +static struct ata_port_info ixp4xx_port_info = { + .flags = ATA_FLAG_NO_ATAPI, + .pio_mask = ATA_PIO4, + .port_ops = &ixp4xx_port_ops, }; static void ixp4xx_setup_port(struct ata_port *ap, - struct ixp4xx_pata_data *data, - unsigned long raw_cs0, unsigned long raw_cs1) + struct ixp4xx_pata *ixpp, + unsigned long raw_cmd, unsigned long raw_ctl) { struct ata_ioports *ioaddr = &ap->ioaddr; - unsigned long raw_cmd = raw_cs0; - unsigned long raw_ctl = raw_cs1 + 0x06; - ioaddr->cmd_addr = data->cs0; - ioaddr->altstatus_addr = data->cs1 + 0x06; - ioaddr->ctl_addr = data->cs1 + 0x06; + raw_ctl += 0x06; + ioaddr->cmd_addr = ixpp->cmd; + ioaddr->altstatus_addr = ixpp->ctl + 0x06; + ioaddr->ctl_addr = ixpp->ctl + 0x06; ata_sff_std_ports(ioaddr); -#ifndef __ARMEB__ - - /* adjust the addresses to handle the address swizzling of the - * ixp4xx in little endian mode. - */ - - *(unsigned long *)&ioaddr->data_addr ^= 0x02; - *(unsigned long *)&ioaddr->cmd_addr ^= 0x03; - *(unsigned long *)&ioaddr->altstatus_addr ^= 0x03; - *(unsigned long *)&ioaddr->ctl_addr ^= 0x03; - *(unsigned long *)&ioaddr->error_addr ^= 0x03; - *(unsigned long *)&ioaddr->feature_addr ^= 0x03; - *(unsigned long *)&ioaddr->nsect_addr ^= 0x03; - *(unsigned long *)&ioaddr->lbal_addr ^= 0x03; - *(unsigned long *)&ioaddr->lbam_addr ^= 0x03; - *(unsigned long *)&ioaddr->lbah_addr ^= 0x03; - *(unsigned long *)&ioaddr->device_addr ^= 0x03; - *(unsigned long *)&ioaddr->status_addr ^= 0x03; - *(unsigned long *)&ioaddr->command_addr ^= 0x03; - - raw_cmd ^= 0x03; - raw_ctl ^= 0x03; -#endif + if (!IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) { + /* adjust the addresses to handle the address swizzling of the + * ixp4xx in little endian mode. + */ + + *(unsigned long *)&ioaddr->data_addr ^= 0x02; + *(unsigned long *)&ioaddr->cmd_addr ^= 0x03; + *(unsigned long *)&ioaddr->altstatus_addr ^= 0x03; + *(unsigned long *)&ioaddr->ctl_addr ^= 0x03; + *(unsigned long *)&ioaddr->error_addr ^= 0x03; + *(unsigned long *)&ioaddr->feature_addr ^= 0x03; + *(unsigned long *)&ioaddr->nsect_addr ^= 0x03; + *(unsigned long *)&ioaddr->lbal_addr ^= 0x03; + *(unsigned long *)&ioaddr->lbam_addr ^= 0x03; + *(unsigned long *)&ioaddr->lbah_addr ^= 0x03; + *(unsigned long *)&ioaddr->device_addr ^= 0x03; + *(unsigned long *)&ioaddr->status_addr ^= 0x03; + *(unsigned long *)&ioaddr->command_addr ^= 0x03; + + raw_cmd ^= 0x03; + raw_ctl ^= 0x03; + } ata_port_desc(ap, "cmd 0x%lx ctl 0x%lx", raw_cmd, raw_ctl); } static int ixp4xx_pata_probe(struct platform_device *pdev) { - struct resource *cs0, *cs1; - struct ata_host *host; - struct ata_port *ap; - struct ixp4xx_pata_data *data = dev_get_platdata(&pdev->dev); + struct resource *cmd, *ctl; + struct ata_port_info pi = ixp4xx_port_info; + const struct ata_port_info *ppi[] = { &pi, NULL }; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct ixp4xx_pata *ixpp; + u32 csindex; int ret; int irq; - cs0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); - cs1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + cmd = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctl = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!cs0 || !cs1) + if (!cmd || !ctl) return -EINVAL; - /* allocate host */ - host = ata_host_alloc(&pdev->dev, 1); - if (!host) + ixpp = devm_kzalloc(dev, sizeof(*ixpp), GFP_KERNEL); + if (!ixpp) return -ENOMEM; - /* acquire resources and fill host */ - ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + ixpp->rmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(ixpp->rmap)) + return dev_err_probe(dev, PTR_ERR(ixpp->rmap), "no regmap\n"); + /* Inspect our address to figure out what chipselect the CMD is on */ + ret = of_property_read_u32_index(np, "reg", 0, &csindex); if (ret) - return ret; + return dev_err_probe(dev, ret, "can't inspect CMD address\n"); + dev_info(dev, "using CS%d for PIO timing configuration\n", csindex); + ixpp->cmd_csreg = csindex * IXP4XX_EXP_TIMING_STRIDE; - data->cs0 = devm_ioremap(&pdev->dev, cs0->start, 0x1000); - data->cs1 = devm_ioremap(&pdev->dev, cs1->start, 0x1000); + ixpp->host = ata_host_alloc_pinfo(dev, ppi, 1); + if (!ixpp->host) + return -ENOMEM; + ixpp->host->private_data = ixpp; - if (!data->cs0 || !data->cs1) + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + ixpp->cmd = devm_ioremap_resource(dev, cmd); + ixpp->ctl = devm_ioremap_resource(dev, ctl); + if (IS_ERR(ixpp->cmd) || IS_ERR(ixpp->ctl)) return -ENOMEM; irq = platform_get_irq(pdev, 0); @@ -173,27 +283,23 @@ static int ixp4xx_pata_probe(struct platform_device *pdev) else return -EINVAL; - /* Setup expansion bus chip selects */ - *data->cs0_cfg = data->cs0_bits; - *data->cs1_cfg = data->cs1_bits; - - ap = host->ports[0]; - - ap->ops = &ixp4xx_port_ops; - ap->pio_mask = ATA_PIO4; - ap->flags |= ATA_FLAG_NO_ATAPI; + /* Just one port to set up */ + ixp4xx_setup_port(ixpp->host->ports[0], ixpp, cmd->start, ctl->start); - ixp4xx_setup_port(ap, data, cs0->start, cs1->start); + ata_print_version_once(dev, DRV_VERSION); - ata_print_version_once(&pdev->dev, DRV_VERSION); - - /* activate host */ - return ata_host_activate(host, irq, ata_sff_interrupt, 0, &ixp4xx_sht); + return ata_host_activate(ixpp->host, irq, ata_sff_interrupt, 0, &ixp4xx_sht); } +static const struct of_device_id ixp4xx_pata_of_match[] = { + { .compatible = "intel,ixp4xx-compact-flash", }, + { }, +}; + static struct platform_driver ixp4xx_pata_platform_driver = { .driver = { .name = DRV_NAME, + .of_match_table = ixp4xx_pata_of_match, }, .probe = ixp4xx_pata_probe, .remove = ata_platform_remove_one, diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index e7f7eee6ee9a..a5b96f3aad67 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -95,6 +95,17 @@ config IMX_WEIM The WEIM(Wireless External Interface Module) works like a bus. You can attach many different devices on it, such as NOR, onenand. +config INTEL_IXP4XX_EB + bool "Intel IXP4xx expansion bus interface driver" + depends on HAS_IOMEM + depends on ARCH_IXP4XX || COMPILE_TEST + default ARCH_IXP4XX + select MFD_SYSCON + help + Driver for the Intel IXP4xx expansion bus interface. The driver is + needed to set up various chip select configuration parameters before + devices on the expansion bus can be discovered. + config MIPS_CDMM bool "MIPS Common Device Memory Map (CDMM) Driver" depends on CPU_MIPSR2 || CPU_MIPSR5 diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 397e35392bff..1c29c5e8ffb8 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_FSL_MC_BUS) += fsl-mc/ obj-$(CONFIG_BT1_APB) += bt1-apb.o obj-$(CONFIG_BT1_AXI) += bt1-axi.o obj-$(CONFIG_IMX_WEIM) += imx-weim.o +obj-$(CONFIG_INTEL_IXP4XX_EB) += intel-ixp4xx-eb.o obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o diff --git a/drivers/bus/intel-ixp4xx-eb.c b/drivers/bus/intel-ixp4xx-eb.c new file mode 100644 index 000000000000..9acd00da413c --- /dev/null +++ b/drivers/bus/intel-ixp4xx-eb.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel IXP4xx Expansion Bus Controller + * Copyright (C) 2021 Linaro Ltd. + * + * Author: Linus Walleij <linus.walleij@linaro.org> + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/log2.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define IXP4XX_EXP_NUM_CS 8 + +#define IXP4XX_EXP_TIMING_CS0 0x00 +#define IXP4XX_EXP_TIMING_CS1 0x04 +#define IXP4XX_EXP_TIMING_CS2 0x08 +#define IXP4XX_EXP_TIMING_CS3 0x0c +#define IXP4XX_EXP_TIMING_CS4 0x10 +#define IXP4XX_EXP_TIMING_CS5 0x14 +#define IXP4XX_EXP_TIMING_CS6 0x18 +#define IXP4XX_EXP_TIMING_CS7 0x1c + +/* Bits inside each CS timing register */ +#define IXP4XX_EXP_TIMING_STRIDE 0x04 +#define IXP4XX_EXP_CS_EN BIT(31) +#define IXP456_EXP_PAR_EN BIT(30) /* Only on IXP45x and IXP46x */ +#define IXP4XX_EXP_T1_MASK GENMASK(28, 27) +#define IXP4XX_EXP_T1_SHIFT 28 +#define IXP4XX_EXP_T2_MASK GENMASK(27, 26) +#define IXP4XX_EXP_T2_SHIFT 26 +#define IXP4XX_EXP_T3_MASK GENMASK(25, 22) +#define IXP4XX_EXP_T3_SHIFT 22 +#define IXP4XX_EXP_T4_MASK GENMASK(21, 20) +#define IXP4XX_EXP_T4_SHIFT 20 +#define IXP4XX_EXP_T5_MASK GENMASK(19, 16) +#define IXP4XX_EXP_T5_SHIFT 16 +#define IXP4XX_EXP_CYC_TYPE_MASK GENMASK(15, 14) +#define IXP4XX_EXP_CYC_TYPE_SHIFT 14 +#define IXP4XX_EXP_SIZE_MASK GENMASK(13, 10) +#define IXP4XX_EXP_SIZE_SHIFT 10 +#define IXP4XX_EXP_CNFG_0 BIT(9) /* Always zero */ +#define IXP43X_EXP_SYNC_INTEL BIT(8) /* Only on IXP43x */ +#define IXP43X_EXP_EXP_CHIP BIT(7) /* Only on IXP43x */ +#define IXP4XX_EXP_BYTE_RD16 BIT(6) +#define IXP4XX_EXP_HRDY_POL BIT(5) /* Only on IXP42x */ +#define IXP4XX_EXP_MUX_EN BIT(4) +#define IXP4XX_EXP_SPLT_EN BIT(3) +#define IXP4XX_EXP_WORD BIT(2) /* Always zero */ +#define IXP4XX_EXP_WR_EN BIT(1) +#define IXP4XX_EXP_BYTE_EN BIT(0) +#define IXP42X_RESERVED (BIT(30)|IXP4XX_EXP_CNFG_0|BIT(8)|BIT(7)|IXP4XX_EXP_WORD) +#define IXP43X_RESERVED (BIT(30)|IXP4XX_EXP_CNFG_0|BIT(5)|IXP4XX_EXP_WORD) + +#define IXP4XX_EXP_CNFG0 0x20 +#define IXP4XX_EXP_CNFG0_MEM_MAP BIT(31) +#define IXP4XX_EXP_CNFG1 0x24 + +#define IXP4XX_EXP_BOOT_BASE 0x00000000 +#define IXP4XX_EXP_NORMAL_BASE 0x50000000 +#define IXP4XX_EXP_STRIDE 0x01000000 + +/* Fuses on the IXP43x */ +#define IXP43X_EXP_UNIT_FUSE_RESET 0x28 +#define IXP43x_EXP_FUSE_SPEED_MASK GENMASK(23, 22) + +/* Number of device tree values in "reg" */ +#define IXP4XX_OF_REG_SIZE 3 + +struct ixp4xx_eb { + struct device *dev; + struct regmap *rmap; + u32 bus_base; + bool is_42x; + bool is_43x; +}; + +struct ixp4xx_exp_tim_prop { + const char *prop; + u32 max; + u32 mask; + u16 shift; +}; + +static const struct ixp4xx_exp_tim_prop ixp4xx_exp_tim_props[] = { + { + .prop = "intel,ixp4xx-eb-t1", + .max = 3, + .mask = IXP4XX_EXP_T1_MASK, + .shift = IXP4XX_EXP_T1_SHIFT, + }, + { + .prop = "intel,ixp4xx-eb-t2", + .max = 3, + .mask = IXP4XX_EXP_T2_MASK, + .shift = IXP4XX_EXP_T2_SHIFT, + }, + { + .prop = "intel,ixp4xx-eb-t3", + .max = 15, + .mask = IXP4XX_EXP_T3_MASK, + .shift = IXP4XX_EXP_T3_SHIFT, + }, + { + .prop = "intel,ixp4xx-eb-t4", + .max = 3, + .mask = IXP4XX_EXP_T4_MASK, + .shift = IXP4XX_EXP_T4_SHIFT, + }, + { + .prop = "intel,ixp4xx-eb-t5", + .max = 15, + .mask = IXP4XX_EXP_T5_MASK, + .shift = IXP4XX_EXP_T5_SHIFT, + }, + { + .prop = "intel,ixp4xx-eb-byte-access-on-halfword", + .max = 1, + .mask = IXP4XX_EXP_BYTE_RD16, + }, + { + .prop = "intel,ixp4xx-eb-hpi-hrdy-pol-high", + .max = 1, + .mask = IXP4XX_EXP_HRDY_POL, + }, + { + .prop = "intel,ixp4xx-eb-mux-address-and-data", + .max = 1, + .mask = IXP4XX_EXP_MUX_EN, + }, + { + .prop = "intel,ixp4xx-eb-ahb-split-transfers", + .max = 1, + .mask = IXP4XX_EXP_SPLT_EN, + }, + { + .prop = "intel,ixp4xx-eb-write-enable", + .max = 1, + .mask = IXP4XX_EXP_WR_EN, + }, + { + .prop = "intel,ixp4xx-eb-byte-access", + .max = 1, + .mask = IXP4XX_EXP_BYTE_EN, + }, +}; + +static void ixp4xx_exp_setup_chipselect(struct ixp4xx_eb *eb, + struct device_node *np, + u32 cs_index, + u32 cs_size) +{ + u32 cs_cfg; + u32 val; + u32 cur_cssize; + u32 cs_order; + int ret; + int i; + + if (eb->is_42x && (cs_index > 7)) { + dev_err(eb->dev, + "invalid chipselect %u, we only support 0-7\n", + cs_index); + return; + } + if (eb->is_43x && (cs_index > 3)) { + dev_err(eb->dev, + "invalid chipselect %u, we only support 0-3\n", + cs_index); + return; + } + + /* Several chip selects can be joined into one device */ + if (cs_size > IXP4XX_EXP_STRIDE) + cur_cssize = IXP4XX_EXP_STRIDE; + else + cur_cssize = cs_size; + + + /* + * The following will read/modify/write the configuration for one + * chipselect, attempting to leave the boot defaults in place unless + * something is explicitly defined. + */ + regmap_read(eb->rmap, IXP4XX_EXP_TIMING_CS0 + + IXP4XX_EXP_TIMING_STRIDE * cs_index, &cs_cfg); + dev_info(eb->dev, "CS%d at %#08x, size %#08x, config before: %#08x\n", + cs_index, eb->bus_base + IXP4XX_EXP_STRIDE * cs_index, + cur_cssize, cs_cfg); + + /* Size set-up first align to 2^9 .. 2^24 */ + cur_cssize = roundup_pow_of_two(cur_cssize); + if (cur_cssize < 512) + cur_cssize = 512; + cs_order = ilog2(cur_cssize); + if (cs_order < 9 || cs_order > 24) { + dev_err(eb->dev, "illegal size order %d\n", cs_order); + return; + } + dev_dbg(eb->dev, "CS%d size order: %d\n", cs_index, cs_order); + cs_cfg &= ~(IXP4XX_EXP_SIZE_MASK); + cs_cfg |= ((cs_order - 9) << IXP4XX_EXP_SIZE_SHIFT); + + for (i = 0; i < ARRAY_SIZE(ixp4xx_exp_tim_props); i++) { + const struct ixp4xx_exp_tim_prop *ip = &ixp4xx_exp_tim_props[i]; + + /* All are regular u32 values */ + ret = of_property_read_u32(np, ip->prop, &val); + if (ret) + continue; + + /* Handle bools (single bits) first */ + if (ip->max == 1) { + if (val) + cs_cfg |= ip->mask; + else + cs_cfg &= ~ip->mask; + dev_info(eb->dev, "CS%d %s %s\n", cs_index, + val ? "enabled" : "disabled", + ip->prop); + continue; + } + + if (val > ip->max) { + dev_err(eb->dev, + "CS%d too high value for %s: %u, capped at %u\n", + cs_index, ip->prop, val, ip->max); + val = ip->max; + } + /* This assumes max value fills all the assigned bits (and it does) */ + cs_cfg &= ~ip->mask; + cs_cfg |= (val << ip->shift); + dev_info(eb->dev, "CS%d set %s to %u\n", cs_index, ip->prop, val); + } + + ret = of_property_read_u32(np, "intel,ixp4xx-eb-cycle-type", &val); + if (!ret) { + if (val > 3) { + dev_err(eb->dev, "illegal cycle type %d\n", val); + return; + } + dev_info(eb->dev, "CS%d set cycle type %d\n", cs_index, val); + cs_cfg &= ~IXP4XX_EXP_CYC_TYPE_MASK; + cs_cfg |= val << IXP4XX_EXP_CYC_TYPE_SHIFT; + } + + if (eb->is_42x) + cs_cfg &= ~IXP42X_RESERVED; + if (eb->is_43x) { + cs_cfg &= ~IXP43X_RESERVED; + /* + * This bit for Intel strata flash is currently unused, but let's + * report it if we find one. + */ + if (cs_cfg & IXP43X_EXP_SYNC_INTEL) + dev_info(eb->dev, "claims to be Intel strata flash\n"); + } + cs_cfg |= IXP4XX_EXP_CS_EN; + + regmap_write(eb->rmap, + IXP4XX_EXP_TIMING_CS0 + IXP4XX_EXP_TIMING_STRIDE * cs_index, + cs_cfg); + dev_info(eb->dev, "CS%d wrote %#08x into CS config\n", cs_index, cs_cfg); + + /* + * If several chip selects are joined together into one big + * device area, we call ourselves recursively for each successive + * chip select. For a 32MB flash chip this results in two calls + * for example. + */ + if (cs_size > IXP4XX_EXP_STRIDE) + ixp4xx_exp_setup_chipselect(eb, np, + cs_index + 1, + cs_size - IXP4XX_EXP_STRIDE); +} + +static void ixp4xx_exp_setup_child(struct ixp4xx_eb *eb, + struct device_node *np) +{ + u32 cs_sizes[IXP4XX_EXP_NUM_CS]; + int num_regs; + u32 csindex; + u32 cssize; + int ret; + int i; + + num_regs = of_property_count_elems_of_size(np, "reg", IXP4XX_OF_REG_SIZE); + if (num_regs <= 0) + return; + dev_dbg(eb->dev, "child %s has %d register sets\n", + of_node_full_name(np), num_regs); + + for (csindex = 0; csindex < IXP4XX_EXP_NUM_CS; csindex++) + cs_sizes[csindex] = 0; + + for (i = 0; i < num_regs; i++) { + u32 rbase, rsize; + + ret = of_property_read_u32_index(np, "reg", + i * IXP4XX_OF_REG_SIZE, &csindex); + if (ret) + break; + ret = of_property_read_u32_index(np, "reg", + i * IXP4XX_OF_REG_SIZE + 1, &rbase); + if (ret) + break; + ret = of_property_read_u32_index(np, "reg", + i * IXP4XX_OF_REG_SIZE + 2, &rsize); + if (ret) + break; + + if (csindex >= IXP4XX_EXP_NUM_CS) { + dev_err(eb->dev, "illegal CS %d\n", csindex); + continue; + } + /* + * The memory window always starts from CS base so we need to add + * the start and size to get to the size from the start of the CS + * base. For example if CS0 is at 0x50000000 and the reg is + * <0 0xe40000 0x40000> the size is e80000. + * + * Roof this if we have several regs setting the same CS. + */ + cssize = rbase + rsize; + dev_dbg(eb->dev, "CS%d size %#08x\n", csindex, cssize); + if (cs_sizes[csindex] < cssize) + cs_sizes[csindex] = cssize; + } + + for (csindex = 0; csindex < IXP4XX_EXP_NUM_CS; csindex++) { + cssize = cs_sizes[csindex]; + if (!cssize) + continue; + /* Just this one, so set it up and return */ + ixp4xx_exp_setup_chipselect(eb, np, csindex, cssize); + } +} + +static int ixp4xx_exp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct ixp4xx_eb *eb; + struct device_node *child; + bool have_children = false; + u32 val; + int ret; + + eb = devm_kzalloc(dev, sizeof(*eb), GFP_KERNEL); + if (!eb) + return -ENOMEM; + + eb->dev = dev; + eb->is_42x = of_device_is_compatible(np, "intel,ixp42x-expansion-bus-controller"); + eb->is_43x = of_device_is_compatible(np, "intel,ixp43x-expansion-bus-controller"); + + eb->rmap = syscon_node_to_regmap(np); + if (IS_ERR(eb->rmap)) + return dev_err_probe(dev, PTR_ERR(eb->rmap), "no regmap\n"); + + /* We check that the regmap work only on first read */ + ret = regmap_read(eb->rmap, IXP4XX_EXP_CNFG0, &val); + if (ret) + dev_err_probe(dev, ret, "cannot read regmap\n"); + if (val & IXP4XX_EXP_CNFG0_MEM_MAP) + eb->bus_base = IXP4XX_EXP_BOOT_BASE; + else + eb->bus_base = IXP4XX_EXP_NORMAL_BASE; + dev_info(dev, "expansion bus at %08x\n", eb->bus_base); + + if (eb->is_43x) { + /* Check some fuses */ + regmap_read(eb->rmap, IXP43X_EXP_UNIT_FUSE_RESET, &val); + switch (FIELD_GET(IXP43x_EXP_FUSE_SPEED_MASK, val)) { + case 0: + dev_info(dev, "IXP43x at 533 MHz\n"); + break; + case 1: + dev_info(dev, "IXP43x at 400 MHz\n"); + break; + case 2: + dev_info(dev, "IXP43x at 667 MHz\n"); + break; + default: + dev_info(dev, "IXP43x unknown speed\n"); + break; + } + } + + /* Walk over the child nodes and see what chipselects we use */ + for_each_available_child_of_node(np, child) { + ixp4xx_exp_setup_child(eb, child); + /* We have at least one child */ + have_children = true; + } + + if (have_children) + return of_platform_default_populate(np, NULL, dev); + + return 0; +} + +static const struct of_device_id ixp4xx_exp_of_match[] = { + { .compatible = "intel,ixp42x-expansion-bus-controller", }, + { .compatible = "intel,ixp43x-expansion-bus-controller", }, + { .compatible = "intel,ixp45x-expansion-bus-controller", }, + { .compatible = "intel,ixp46x-expansion-bus-controller", }, + { } +}; + +static struct platform_driver ixp4xx_exp_driver = { + .probe = ixp4xx_exp_probe, + .driver = { + .name = "intel-extbus", + .of_match_table = ixp4xx_exp_of_match, + }, +}; +module_platform_driver(ixp4xx_exp_driver); +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("Intel IXP4xx external bus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clocksource/timer-ixp4xx.c b/drivers/clocksource/timer-ixp4xx.c index 9396745e1c17..cbb184953510 100644 --- a/drivers/clocksource/timer-ixp4xx.c +++ b/drivers/clocksource/timer-ixp4xx.c @@ -18,6 +18,7 @@ #include <linux/delay.h> #include <linux/of_address.h> #include <linux/of_irq.h> +#include <linux/platform_device.h> /* Goes away with OF conversion */ #include <linux/platform_data/timer-ixp4xx.h> @@ -29,9 +30,6 @@ #define IXP4XX_OSRT1_OFFSET 0x08 /* Timer 1 Reload */ #define IXP4XX_OST2_OFFSET 0x0C /* Timer 2 Timestamp */ #define IXP4XX_OSRT2_OFFSET 0x10 /* Timer 2 Reload */ -#define IXP4XX_OSWT_OFFSET 0x14 /* Watchdog Timer */ -#define IXP4XX_OSWE_OFFSET 0x18 /* Watchdog Enable */ -#define IXP4XX_OSWK_OFFSET 0x1C /* Watchdog Key */ #define IXP4XX_OSST_OFFSET 0x20 /* Timer Status */ /* @@ -45,17 +43,10 @@ #define IXP4XX_OSST_TIMER_1_PEND 0x00000001 #define IXP4XX_OSST_TIMER_2_PEND 0x00000002 #define IXP4XX_OSST_TIMER_TS_PEND 0x00000004 -#define IXP4XX_OSST_TIMER_WDOG_PEND 0x00000008 -#define IXP4XX_OSST_TIMER_WARM_RESET 0x00000010 - -#define IXP4XX_WDT_KEY 0x0000482E -#define IXP4XX_WDT_RESET_ENABLE 0x00000001 -#define IXP4XX_WDT_IRQ_ENABLE 0x00000002 -#define IXP4XX_WDT_COUNT_ENABLE 0x00000004 +/* Remaining registers are for the watchdog and defined in the watchdog driver */ struct ixp4xx_timer { void __iomem *base; - unsigned int tick_rate; u32 latch; struct clock_event_device clkevt; #ifdef CONFIG_ARM @@ -181,7 +172,6 @@ static __init int ixp4xx_timer_register(void __iomem *base, if (!tmr) return -ENOMEM; tmr->base = base; - tmr->tick_rate = timer_freq; /* * The timer register doesn't allow to specify the two least @@ -239,6 +229,40 @@ static __init int ixp4xx_timer_register(void __iomem *base, return 0; } +static struct platform_device ixp4xx_watchdog_device = { + .name = "ixp4xx-watchdog", + .id = -1, +}; + +/* + * This probe gets called after the timer is already up and running. The main + * function on this platform is to spawn the watchdog device as a child. + */ +static int ixp4xx_timer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + /* Pass the base address as platform data and nothing else */ + ixp4xx_watchdog_device.dev.platform_data = local_ixp4xx_timer->base; + ixp4xx_watchdog_device.dev.parent = dev; + return platform_device_register(&ixp4xx_watchdog_device); +} + +static const struct of_device_id ixp4xx_timer_dt_id[] = { + { .compatible = "intel,ixp4xx-timer", }, + { /* sentinel */ }, +}; + +static struct platform_driver ixp4xx_timer_driver = { + .probe = ixp4xx_timer_probe, + .driver = { + .name = "ixp4xx-timer", + .of_match_table = ixp4xx_timer_dt_id, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ixp4xx_timer_driver); + /** * ixp4xx_timer_setup() - Timer setup function to be called from boardfiles * @timerbase: physical base of timer block diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 546dfc1e2349..0bc7046ab942 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -487,6 +487,7 @@ config FTWDT010_WATCHDOG config IXP4XX_WATCHDOG tristate "IXP4xx Watchdog" depends on ARCH_IXP4XX + select WATCHDOG_CORE help Say Y here if to include support for the watchdog timer in the Intel IXP4xx network processors. This driver can diff --git a/drivers/watchdog/ixp4xx_wdt.c b/drivers/watchdog/ixp4xx_wdt.c index aae29dcfaf11..2693ffb24ac7 100644 --- a/drivers/watchdog/ixp4xx_wdt.c +++ b/drivers/watchdog/ixp4xx_wdt.c @@ -1,220 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/char/watchdog/ixp4xx_wdt.c * * Watchdog driver for Intel IXP4xx network processors * * Author: Deepak Saxena <dsaxena@plexity.net> + * Author: Linus Walleij <linus.walleij@linaro.org> * * Copyright 2004 (c) MontaVista, Software, Inc. * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/types.h> #include <linux/kernel.h> -#include <linux/fs.h> -#include <linux/miscdevice.h> -#include <linux/of.h> #include <linux/watchdog.h> -#include <linux/init.h> -#include <linux/bitops.h> -#include <linux/uaccess.h> -#include <mach/hardware.h> +#include <linux/bits.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/soc/ixp4xx/cpu.h> + +struct ixp4xx_wdt { + struct watchdog_device wdd; + void __iomem *base; + unsigned long rate; +}; -static bool nowayout = WATCHDOG_NOWAYOUT; -static int heartbeat = 60; /* (secs) Default is 1 minute */ -static unsigned long wdt_status; -static unsigned long boot_status; -static DEFINE_SPINLOCK(wdt_lock); +/* Fallback if we do not have a clock for this */ +#define IXP4XX_TIMER_FREQ 66666000 -#define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL) +/* Registers after the timer registers */ +#define IXP4XX_OSWT_OFFSET 0x14 /* Watchdog Timer */ +#define IXP4XX_OSWE_OFFSET 0x18 /* Watchdog Enable */ +#define IXP4XX_OSWK_OFFSET 0x1C /* Watchdog Key */ +#define IXP4XX_OSST_OFFSET 0x20 /* Timer Status */ -#define WDT_IN_USE 0 -#define WDT_OK_TO_CLOSE 1 +#define IXP4XX_OSST_TIMER_WDOG_PEND 0x00000008 +#define IXP4XX_OSST_TIMER_WARM_RESET 0x00000010 +#define IXP4XX_WDT_KEY 0x0000482E +#define IXP4XX_WDT_RESET_ENABLE 0x00000001 +#define IXP4XX_WDT_IRQ_ENABLE 0x00000002 +#define IXP4XX_WDT_COUNT_ENABLE 0x00000004 -static void wdt_enable(void) +static inline +struct ixp4xx_wdt *to_ixp4xx_wdt(struct watchdog_device *wdd) { - spin_lock(&wdt_lock); - *IXP4XX_OSWK = IXP4XX_WDT_KEY; - *IXP4XX_OSWE = 0; - *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat; - *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE; - *IXP4XX_OSWK = 0; - spin_unlock(&wdt_lock); + return container_of(wdd, struct ixp4xx_wdt, wdd); } -static void wdt_disable(void) +static int ixp4xx_wdt_start(struct watchdog_device *wdd) { - spin_lock(&wdt_lock); - *IXP4XX_OSWK = IXP4XX_WDT_KEY; - *IXP4XX_OSWE = 0; - *IXP4XX_OSWK = 0; - spin_unlock(&wdt_lock); -} + struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd); -static int ixp4xx_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(WDT_IN_USE, &wdt_status)) - return -EBUSY; + __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET); + __raw_writel(0, iwdt->base + IXP4XX_OSWE_OFFSET); + __raw_writel(wdd->timeout * iwdt->rate, + iwdt->base + IXP4XX_OSWT_OFFSET); + __raw_writel(IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE, + iwdt->base + IXP4XX_OSWE_OFFSET); + __raw_writel(0, iwdt->base + IXP4XX_OSWK_OFFSET); - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - wdt_enable(); - return stream_open(inode, file); + return 0; } -static ssize_t -ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +static int ixp4xx_wdt_stop(struct watchdog_device *wdd) { - if (len) { - if (!nowayout) { - size_t i; - - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - set_bit(WDT_OK_TO_CLOSE, &wdt_status); - } - } - wdt_enable(); - } - return len; -} + struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd); -static const struct watchdog_info ident = { - .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | - WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .identity = "IXP4xx Watchdog", -}; + __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET); + __raw_writel(0, iwdt->base + IXP4XX_OSWE_OFFSET); + __raw_writel(0, iwdt->base + IXP4XX_OSWK_OFFSET); - -static long ixp4xx_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - int ret = -ENOTTY; - int time; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user((struct watchdog_info *)arg, &ident, - sizeof(ident)) ? -EFAULT : 0; - break; - - case WDIOC_GETSTATUS: - ret = put_user(0, (int *)arg); - break; - - case WDIOC_GETBOOTSTATUS: - ret = put_user(boot_status, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(time, (int *)arg); - if (ret) - break; - - if (time <= 0 || time > 60) { - ret = -EINVAL; - break; - } - - heartbeat = time; - wdt_enable(); - fallthrough; - - case WDIOC_GETTIMEOUT: - ret = put_user(heartbeat, (int *)arg); - break; - } - return ret; + return 0; } -static int ixp4xx_wdt_release(struct inode *inode, struct file *file) +static int ixp4xx_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) { - if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) - wdt_disable(); - else - pr_crit("Device closed unexpectedly - timer will not stop\n"); - clear_bit(WDT_IN_USE, &wdt_status); - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + wdd->timeout = timeout; + if (watchdog_active(wdd)) + ixp4xx_wdt_start(wdd); return 0; } - -static const struct file_operations ixp4xx_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = ixp4xx_wdt_write, - .unlocked_ioctl = ixp4xx_wdt_ioctl, - .compat_ioctl = compat_ptr_ioctl, - .open = ixp4xx_wdt_open, - .release = ixp4xx_wdt_release, +static const struct watchdog_ops ixp4xx_wdt_ops = { + .start = ixp4xx_wdt_start, + .stop = ixp4xx_wdt_stop, + .set_timeout = ixp4xx_wdt_set_timeout, + .owner = THIS_MODULE, }; -static struct miscdevice ixp4xx_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &ixp4xx_wdt_fops, +static const struct watchdog_info ixp4xx_wdt_info = { + .options = WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE + | WDIOF_SETTIMEOUT, + .identity = KBUILD_MODNAME, }; -static int __init ixp4xx_wdt_init(void) +/* Devres-handled clock disablement */ +static void ixp4xx_clock_action(void *d) +{ + clk_disable_unprepare(d); +} + +static int ixp4xx_wdt_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct ixp4xx_wdt *iwdt; + struct clk *clk; int ret; - /* - * FIXME: we bail out on device tree boot but this really needs - * to be fixed in a nicer way: this registers the MDIO bus before - * even matching the driver infrastructure, we should only probe - * detected hardware. - */ - if (of_have_populated_dt()) - return -ENODEV; if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) { - pr_err("Rev. A0 IXP42x CPU detected - watchdog disabled\n"); - + dev_err(dev, "Rev. A0 IXP42x CPU detected - watchdog disabled\n"); return -ENODEV; } - boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ? - WDIOF_CARDRESET : 0; - ret = misc_register(&ixp4xx_wdt_miscdev); - if (ret == 0) - pr_info("timer heartbeat %d sec\n", heartbeat); - return ret; -} -static void __exit ixp4xx_wdt_exit(void) -{ - misc_deregister(&ixp4xx_wdt_miscdev); -} + iwdt = devm_kzalloc(dev, sizeof(*iwdt), GFP_KERNEL); + if (!iwdt) + return -ENOMEM; + iwdt->base = dev->platform_data; + /* + * Retrieve rate from a fixed clock from the device tree if + * the parent has that, else use the default clock rate. + */ + clk = devm_clk_get(dev->parent, NULL); + if (!IS_ERR(clk)) { + ret = clk_prepare_enable(clk); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, ixp4xx_clock_action, clk); + if (ret) + return ret; + iwdt->rate = clk_get_rate(clk); + } + if (!iwdt->rate) + iwdt->rate = IXP4XX_TIMER_FREQ; -module_init(ixp4xx_wdt_init); -module_exit(ixp4xx_wdt_exit); + iwdt->wdd.info = &ixp4xx_wdt_info; + iwdt->wdd.ops = &ixp4xx_wdt_ops; + iwdt->wdd.min_timeout = 1; + iwdt->wdd.max_timeout = U32_MAX / iwdt->rate; + iwdt->wdd.parent = dev; + /* Default to 60 seconds */ + iwdt->wdd.timeout = 60U; + watchdog_init_timeout(&iwdt->wdd, 0, dev); -MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); -MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); + if (__raw_readl(iwdt->base + IXP4XX_OSST_OFFSET) & + IXP4XX_OSST_TIMER_WARM_RESET) + iwdt->wdd.bootstatus = WDIOF_CARDRESET; + + ret = devm_watchdog_register_device(dev, &iwdt->wdd); + if (ret) + return ret; + + dev_info(dev, "IXP4xx watchdog available\n"); -module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); + return 0; +} -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); +static struct platform_driver ixp4xx_wdt_driver = { + .probe = ixp4xx_wdt_probe, + .driver = { + .name = "ixp4xx-watchdog", + }, +}; +module_platform_driver(ixp4xx_wdt_driver); +MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); +MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/pata_ixp4xx_cf.h b/include/linux/platform_data/pata_ixp4xx_cf.h index 601ba97fef57..e60fa41da4a5 100644 --- a/include/linux/platform_data/pata_ixp4xx_cf.h +++ b/include/linux/platform_data/pata_ixp4xx_cf.h @@ -14,8 +14,8 @@ struct ixp4xx_pata_data { volatile u32 *cs1_cfg; unsigned long cs0_bits; unsigned long cs1_bits; - void __iomem *cs0; - void __iomem *cs1; + void __iomem *cmd; + void __iomem *ctl; }; #endif |