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.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEElDRnuGcz/wPCXQWMQRCzN7AZXXMFAmEKauIACgkQQRCzN7AZ
 XXOuvQ//dUpnPho0e5M+2sKTzM2Gh0o5GvFzzCGHk6CtiF4diifhCTUL8tFdZGM3
 ZkYYliksgT0dQ3A0FLndOJmH9xgISkYwOwPh0Cvybolr3JZ2+2btHMFK5j81IYJp
 OCTC5mX77Ezw+VsqyRfArt/UKQzw0xet46xPZEp2xBJBzRd4zcNVDpNW2yNNYZ89
 2wUmxU5YJknvD5WOM7BPDBBPr6FzczmmDlCtpxGiNwKprhTp0vdJJxEEphtjXXim
 qOAjFMG8H4hZhpM8iK44hbMr5yBbZXUH7nwhEDOiEeQMrDCYOU6sr33rEYOmXUG2
 dpOK6NZBrNBSK1rlbZ5FbHYMpMX1fCTBFX5TO3RZvyul8n5t0SBLe1grn2It9yTG
 wyAwnA0KCdDbg+6Xgn0X/pEvpr4l2W8Up1/CO8wvrDYQXu9YOp+5of2Nm1iBkOF0
 w48GoBWMJBlRK8a4UAvqGFskkf1Sh5+P6zCnmUWdVjW4l94QZJ6QNhL25Znj3WhF
 Mz8+yVCv/LM1lOiWh0AxytejP2DmYgIbXrkc4ctaH5UtR9R5BTT+QtCHDuipxWeC
 nC5ucPlw2/XKyNxiy9ooBxo19XaK9rmUdagT4ViGxvhlopx20FYslAVMg6XSGruP
 HE22zsHyZanNMjdmGv6guvQsZm9PC68JBbwv8J0JQQaouR49rAk=
 =v2E7
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmEKnj0ACgkQmmx57+YA
 GNn+UBAAiZI7jAfykKZ/r0IHlspI88deTvfiDtBsMmPHtRfW67yFsRzKmU/TL21y
 ae7tPVOMgfPJ+AXLbARh0KnpmIXm++A7lY2PJezth38Gxm5Gx8rGbX41V6w6ZFH4
 Zn+lJR6pv+wccqz7QqEWPhKrbvYybCdwYpFjkZUUjyxMjjiWCXvl+M3PaIKtVITj
 a7t+hLFJ/h6IbBX3igZZG/fXfQh30HSrmaEzA08Uuamn3EUfYof+xfLAZ0xwhmnh
 v9E5b27yRRhxir6nUFPlqt9+meAqFsK6eqrb5aZpjloBUJas8mkcOnF8Ch0d/0jn
 TJJMImxU3eoeWi/8LHFSUl65Xxo8hTAwtpC5fMseDh0n0p5aFL5D6gbKeJ89PgeO
 HRUc1YG05IgtF+70TyldxtLMdZwyBIwFMTIBaJPbT/Zz21LjZbep6sEIZkKGuE8I
 n5p1tGX2Hjxqfcfk6Ftl+NY4V80BX+kq0esU0RT0Bve0swed22GIq0tH/8680ln8
 G61pDuQqVEedxq8WrxfXdptb5iBOkiNI7HgBmvkpw0U986BwGtqFrYBKeFYj/wOQ
 LGcqWSGaFcofDIySKrTsR8im51CHwKou+hrFusItFlcCjC4DPcMVo0+1BdYfp0Zo
 vQ09IaHb209uw62YjBQe4MaAQxmm21zzLEqk6LUNd4hyEpOdx2w=
 =2bLl
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Arnd Bergmann 2021-08-04 16:03:40 +02:00
commit 775dea4dee
11 changed files with 1028 additions and 272 deletions

View file

@ -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>;
};
};
...

View file

@ -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>;
};
};

View file

@ -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

View file

@ -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)
{
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_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;
}
return 0;
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)
{
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;
}
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__
if (!IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) {
/* adjust the addresses to handle the address swizzling of the
* ixp4xx in little endian mode.
*/
/* 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;
*(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
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 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;
ixpp->host = ata_host_alloc_pinfo(dev, ppi, 1);
if (!ixpp->host)
return -ENOMEM;
ixpp->host->private_data = ixpp;
ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
if (ret)
return ret;
data->cs0 = devm_ioremap(&pdev->dev, cs0->start, 0x1000);
data->cs1 = devm_ioremap(&pdev->dev, cs1->start, 0x1000);
if (!data->cs0 || !data->cs1)
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;
/* Just one port to set up */
ixp4xx_setup_port(ixpp->host->ports[0], ixpp, cmd->start, ctl->start);
ap = host->ports[0];
ata_print_version_once(dev, DRV_VERSION);
ap->ops = &ixp4xx_port_ops;
ap->pio_mask = ATA_PIO4;
ap->flags |= ATA_FLAG_NO_ATAPI;
ixp4xx_setup_port(ap, data, cs0->start, cs1->start);
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,

View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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>
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);
#define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL)
#define WDT_IN_USE 0
#define WDT_OK_TO_CLOSE 1
static void wdt_enable(void)
{
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);
}
static void wdt_disable(void)
{
spin_lock(&wdt_lock);
*IXP4XX_OSWK = IXP4XX_WDT_KEY;
*IXP4XX_OSWE = 0;
*IXP4XX_OSWK = 0;
spin_unlock(&wdt_lock);
}
static int ixp4xx_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(WDT_IN_USE, &wdt_status))
return -EBUSY;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
wdt_enable();
return stream_open(inode, file);
}
static ssize_t
ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
{
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;
}
static const struct watchdog_info ident = {
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "IXP4xx Watchdog",
struct ixp4xx_wdt {
struct watchdog_device wdd;
void __iomem *base;
unsigned long rate;
};
/* Fallback if we do not have a clock for this */
#define IXP4XX_TIMER_FREQ 66666000
static long ixp4xx_wdt_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
/* 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 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 inline
struct ixp4xx_wdt *to_ixp4xx_wdt(struct watchdog_device *wdd)
{
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 container_of(wdd, struct ixp4xx_wdt, wdd);
}
static int ixp4xx_wdt_release(struct inode *inode, struct file *file)
static int ixp4xx_wdt_start(struct watchdog_device *wdd)
{
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);
struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd);
__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);
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 struct miscdevice ixp4xx_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &ixp4xx_wdt_fops,
};
static int __init ixp4xx_wdt_init(void)
static int ixp4xx_wdt_stop(struct watchdog_device *wdd)
{
struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd);
__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);
return 0;
}
static int ixp4xx_wdt_set_timeout(struct watchdog_device *wdd,
unsigned int timeout)
{
wdd->timeout = timeout;
if (watchdog_active(wdd))
ixp4xx_wdt_start(wdd);
return 0;
}
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 const struct watchdog_info ixp4xx_wdt_info = {
.options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT,
.identity = KBUILD_MODNAME,
};
/* 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;
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;
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);
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");
return 0;
}
static void __exit ixp4xx_wdt_exit(void)
{
misc_deregister(&ixp4xx_wdt_miscdev);
}
module_init(ixp4xx_wdt_init);
module_exit(ixp4xx_wdt_exit);
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_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
MODULE_LICENSE("GPL");

View file

@ -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