6ff482eeeb
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). In the error path emit an error message replacing the (less useful) message by the core. Apart from the improved error message there is no change in behaviour. Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Link: https://lore.kernel.org/r/1d385231c23c2a1e6e7dc1968eb111327386d1f6.1701619134.git.u.kleine-koenig@pengutronix.de Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
614 lines
13 KiB
C
614 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* sun_esp.c: ESP front-end for Sparc SBUS systems.
|
|
*
|
|
* Copyright (C) 2007, 2008 David S. Miller (davem@davemloft.net)
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/init.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/gfp.h>
|
|
|
|
#include <asm/irq.h>
|
|
#include <asm/io.h>
|
|
#include <asm/dma.h>
|
|
|
|
#include <scsi/scsi_host.h>
|
|
|
|
#include "esp_scsi.h"
|
|
|
|
#define DRV_MODULE_NAME "sun_esp"
|
|
#define PFX DRV_MODULE_NAME ": "
|
|
#define DRV_VERSION "1.100"
|
|
#define DRV_MODULE_RELDATE "August 27, 2008"
|
|
|
|
#define dma_read32(REG) \
|
|
sbus_readl(esp->dma_regs + (REG))
|
|
#define dma_write32(VAL, REG) \
|
|
sbus_writel((VAL), esp->dma_regs + (REG))
|
|
|
|
/* DVMA chip revisions */
|
|
enum dvma_rev {
|
|
dvmarev0,
|
|
dvmaesc1,
|
|
dvmarev1,
|
|
dvmarev2,
|
|
dvmarev3,
|
|
dvmarevplus,
|
|
dvmahme
|
|
};
|
|
|
|
static int esp_sbus_setup_dma(struct esp *esp, struct platform_device *dma_of)
|
|
{
|
|
esp->dma = dma_of;
|
|
|
|
esp->dma_regs = of_ioremap(&dma_of->resource[0], 0,
|
|
resource_size(&dma_of->resource[0]),
|
|
"espdma");
|
|
if (!esp->dma_regs)
|
|
return -ENOMEM;
|
|
|
|
switch (dma_read32(DMA_CSR) & DMA_DEVICE_ID) {
|
|
case DMA_VERS0:
|
|
esp->dmarev = dvmarev0;
|
|
break;
|
|
case DMA_ESCV1:
|
|
esp->dmarev = dvmaesc1;
|
|
break;
|
|
case DMA_VERS1:
|
|
esp->dmarev = dvmarev1;
|
|
break;
|
|
case DMA_VERS2:
|
|
esp->dmarev = dvmarev2;
|
|
break;
|
|
case DMA_VERHME:
|
|
esp->dmarev = dvmahme;
|
|
break;
|
|
case DMA_VERSPLUS:
|
|
esp->dmarev = dvmarevplus;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int esp_sbus_map_regs(struct esp *esp, int hme)
|
|
{
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
struct resource *res;
|
|
|
|
/* On HME, two reg sets exist, first is DVMA,
|
|
* second is ESP registers.
|
|
*/
|
|
if (hme)
|
|
res = &op->resource[1];
|
|
else
|
|
res = &op->resource[0];
|
|
|
|
esp->regs = of_ioremap(res, 0, SBUS_ESP_REG_SIZE, "ESP");
|
|
if (!esp->regs)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int esp_sbus_map_command_block(struct esp *esp)
|
|
{
|
|
esp->command_block = dma_alloc_coherent(esp->dev, 16,
|
|
&esp->command_block_dma,
|
|
GFP_KERNEL);
|
|
if (!esp->command_block)
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
static int esp_sbus_register_irq(struct esp *esp)
|
|
{
|
|
struct Scsi_Host *host = esp->host;
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
|
|
host->irq = op->archdata.irqs[0];
|
|
return request_irq(host->irq, scsi_esp_intr, IRQF_SHARED, "ESP", esp);
|
|
}
|
|
|
|
static void esp_get_scsi_id(struct esp *esp, struct platform_device *espdma)
|
|
{
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
struct device_node *dp;
|
|
|
|
dp = op->dev.of_node;
|
|
esp->scsi_id = of_getintprop_default(dp, "initiator-id", 0xff);
|
|
if (esp->scsi_id != 0xff)
|
|
goto done;
|
|
|
|
esp->scsi_id = of_getintprop_default(dp, "scsi-initiator-id", 0xff);
|
|
if (esp->scsi_id != 0xff)
|
|
goto done;
|
|
|
|
esp->scsi_id = of_getintprop_default(espdma->dev.of_node,
|
|
"scsi-initiator-id", 7);
|
|
|
|
done:
|
|
esp->host->this_id = esp->scsi_id;
|
|
esp->scsi_id_mask = (1 << esp->scsi_id);
|
|
}
|
|
|
|
static void esp_get_differential(struct esp *esp)
|
|
{
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
struct device_node *dp;
|
|
|
|
dp = op->dev.of_node;
|
|
if (of_property_read_bool(dp, "differential"))
|
|
esp->flags |= ESP_FLAG_DIFFERENTIAL;
|
|
else
|
|
esp->flags &= ~ESP_FLAG_DIFFERENTIAL;
|
|
}
|
|
|
|
static void esp_get_clock_params(struct esp *esp)
|
|
{
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
struct device_node *bus_dp, *dp;
|
|
int fmhz;
|
|
|
|
dp = op->dev.of_node;
|
|
bus_dp = dp->parent;
|
|
|
|
fmhz = of_getintprop_default(dp, "clock-frequency", 0);
|
|
if (fmhz == 0)
|
|
fmhz = of_getintprop_default(bus_dp, "clock-frequency", 0);
|
|
|
|
esp->cfreq = fmhz;
|
|
}
|
|
|
|
static void esp_get_bursts(struct esp *esp, struct platform_device *dma_of)
|
|
{
|
|
struct device_node *dma_dp = dma_of->dev.of_node;
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
struct device_node *dp;
|
|
u8 bursts, val;
|
|
|
|
dp = op->dev.of_node;
|
|
bursts = of_getintprop_default(dp, "burst-sizes", 0xff);
|
|
val = of_getintprop_default(dma_dp, "burst-sizes", 0xff);
|
|
if (val != 0xff)
|
|
bursts &= val;
|
|
|
|
val = of_getintprop_default(dma_dp->parent, "burst-sizes", 0xff);
|
|
if (val != 0xff)
|
|
bursts &= val;
|
|
|
|
if (bursts == 0xff ||
|
|
(bursts & DMA_BURST16) == 0 ||
|
|
(bursts & DMA_BURST32) == 0)
|
|
bursts = (DMA_BURST32 - 1);
|
|
|
|
esp->bursts = bursts;
|
|
}
|
|
|
|
static void esp_sbus_get_props(struct esp *esp, struct platform_device *espdma)
|
|
{
|
|
esp_get_scsi_id(esp, espdma);
|
|
esp_get_differential(esp);
|
|
esp_get_clock_params(esp);
|
|
esp_get_bursts(esp, espdma);
|
|
}
|
|
|
|
static void sbus_esp_write8(struct esp *esp, u8 val, unsigned long reg)
|
|
{
|
|
sbus_writeb(val, esp->regs + (reg * 4UL));
|
|
}
|
|
|
|
static u8 sbus_esp_read8(struct esp *esp, unsigned long reg)
|
|
{
|
|
return sbus_readb(esp->regs + (reg * 4UL));
|
|
}
|
|
|
|
static int sbus_esp_irq_pending(struct esp *esp)
|
|
{
|
|
if (dma_read32(DMA_CSR) & (DMA_HNDL_INTR | DMA_HNDL_ERROR))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void sbus_esp_reset_dma(struct esp *esp)
|
|
{
|
|
int can_do_burst16, can_do_burst32, can_do_burst64;
|
|
int can_do_sbus64, lim;
|
|
struct platform_device *op = to_platform_device(esp->dev);
|
|
u32 val;
|
|
|
|
can_do_burst16 = (esp->bursts & DMA_BURST16) != 0;
|
|
can_do_burst32 = (esp->bursts & DMA_BURST32) != 0;
|
|
can_do_burst64 = 0;
|
|
can_do_sbus64 = 0;
|
|
if (sbus_can_dma_64bit())
|
|
can_do_sbus64 = 1;
|
|
if (sbus_can_burst64())
|
|
can_do_burst64 = (esp->bursts & DMA_BURST64) != 0;
|
|
|
|
/* Put the DVMA into a known state. */
|
|
if (esp->dmarev != dvmahme) {
|
|
val = dma_read32(DMA_CSR);
|
|
dma_write32(val | DMA_RST_SCSI, DMA_CSR);
|
|
dma_write32(val & ~DMA_RST_SCSI, DMA_CSR);
|
|
}
|
|
switch (esp->dmarev) {
|
|
case dvmahme:
|
|
dma_write32(DMA_RESET_FAS366, DMA_CSR);
|
|
dma_write32(DMA_RST_SCSI, DMA_CSR);
|
|
|
|
esp->prev_hme_dmacsr = (DMA_PARITY_OFF | DMA_2CLKS |
|
|
DMA_SCSI_DISAB | DMA_INT_ENAB);
|
|
|
|
esp->prev_hme_dmacsr &= ~(DMA_ENABLE | DMA_ST_WRITE |
|
|
DMA_BRST_SZ);
|
|
|
|
if (can_do_burst64)
|
|
esp->prev_hme_dmacsr |= DMA_BRST64;
|
|
else if (can_do_burst32)
|
|
esp->prev_hme_dmacsr |= DMA_BRST32;
|
|
|
|
if (can_do_sbus64) {
|
|
esp->prev_hme_dmacsr |= DMA_SCSI_SBUS64;
|
|
sbus_set_sbus64(&op->dev, esp->bursts);
|
|
}
|
|
|
|
lim = 1000;
|
|
while (dma_read32(DMA_CSR) & DMA_PEND_READ) {
|
|
if (--lim == 0) {
|
|
printk(KERN_ALERT PFX "esp%d: DMA_PEND_READ "
|
|
"will not clear!\n",
|
|
esp->host->unique_id);
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
|
|
dma_write32(0, DMA_CSR);
|
|
dma_write32(esp->prev_hme_dmacsr, DMA_CSR);
|
|
|
|
dma_write32(0, DMA_ADDR);
|
|
break;
|
|
|
|
case dvmarev2:
|
|
if (esp->rev != ESP100) {
|
|
val = dma_read32(DMA_CSR);
|
|
dma_write32(val | DMA_3CLKS, DMA_CSR);
|
|
}
|
|
break;
|
|
|
|
case dvmarev3:
|
|
val = dma_read32(DMA_CSR);
|
|
val &= ~DMA_3CLKS;
|
|
val |= DMA_2CLKS;
|
|
if (can_do_burst32) {
|
|
val &= ~DMA_BRST_SZ;
|
|
val |= DMA_BRST32;
|
|
}
|
|
dma_write32(val, DMA_CSR);
|
|
break;
|
|
|
|
case dvmaesc1:
|
|
val = dma_read32(DMA_CSR);
|
|
val |= DMA_ADD_ENABLE;
|
|
val &= ~DMA_BCNT_ENAB;
|
|
if (!can_do_burst32 && can_do_burst16) {
|
|
val |= DMA_ESC_BURST;
|
|
} else {
|
|
val &= ~(DMA_ESC_BURST);
|
|
}
|
|
dma_write32(val, DMA_CSR);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Enable interrupts. */
|
|
val = dma_read32(DMA_CSR);
|
|
dma_write32(val | DMA_INT_ENAB, DMA_CSR);
|
|
}
|
|
|
|
static void sbus_esp_dma_drain(struct esp *esp)
|
|
{
|
|
u32 csr;
|
|
int lim;
|
|
|
|
if (esp->dmarev == dvmahme)
|
|
return;
|
|
|
|
csr = dma_read32(DMA_CSR);
|
|
if (!(csr & DMA_FIFO_ISDRAIN))
|
|
return;
|
|
|
|
if (esp->dmarev != dvmarev3 && esp->dmarev != dvmaesc1)
|
|
dma_write32(csr | DMA_FIFO_STDRAIN, DMA_CSR);
|
|
|
|
lim = 1000;
|
|
while (dma_read32(DMA_CSR) & DMA_FIFO_ISDRAIN) {
|
|
if (--lim == 0) {
|
|
printk(KERN_ALERT PFX "esp%d: DMA will not drain!\n",
|
|
esp->host->unique_id);
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
}
|
|
|
|
static void sbus_esp_dma_invalidate(struct esp *esp)
|
|
{
|
|
if (esp->dmarev == dvmahme) {
|
|
dma_write32(DMA_RST_SCSI, DMA_CSR);
|
|
|
|
esp->prev_hme_dmacsr = ((esp->prev_hme_dmacsr |
|
|
(DMA_PARITY_OFF | DMA_2CLKS |
|
|
DMA_SCSI_DISAB | DMA_INT_ENAB)) &
|
|
~(DMA_ST_WRITE | DMA_ENABLE));
|
|
|
|
dma_write32(0, DMA_CSR);
|
|
dma_write32(esp->prev_hme_dmacsr, DMA_CSR);
|
|
|
|
/* This is necessary to avoid having the SCSI channel
|
|
* engine lock up on us.
|
|
*/
|
|
dma_write32(0, DMA_ADDR);
|
|
} else {
|
|
u32 val;
|
|
int lim;
|
|
|
|
lim = 1000;
|
|
while ((val = dma_read32(DMA_CSR)) & DMA_PEND_READ) {
|
|
if (--lim == 0) {
|
|
printk(KERN_ALERT PFX "esp%d: DMA will not "
|
|
"invalidate!\n", esp->host->unique_id);
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
|
|
val &= ~(DMA_ENABLE | DMA_ST_WRITE | DMA_BCNT_ENAB);
|
|
val |= DMA_FIFO_INV;
|
|
dma_write32(val, DMA_CSR);
|
|
val &= ~DMA_FIFO_INV;
|
|
dma_write32(val, DMA_CSR);
|
|
}
|
|
}
|
|
|
|
static void sbus_esp_send_dma_cmd(struct esp *esp, u32 addr, u32 esp_count,
|
|
u32 dma_count, int write, u8 cmd)
|
|
{
|
|
u32 csr;
|
|
|
|
BUG_ON(!(cmd & ESP_CMD_DMA));
|
|
|
|
sbus_esp_write8(esp, (esp_count >> 0) & 0xff, ESP_TCLOW);
|
|
sbus_esp_write8(esp, (esp_count >> 8) & 0xff, ESP_TCMED);
|
|
if (esp->rev == FASHME) {
|
|
sbus_esp_write8(esp, (esp_count >> 16) & 0xff, FAS_RLO);
|
|
sbus_esp_write8(esp, 0, FAS_RHI);
|
|
|
|
scsi_esp_cmd(esp, cmd);
|
|
|
|
csr = esp->prev_hme_dmacsr;
|
|
csr |= DMA_SCSI_DISAB | DMA_ENABLE;
|
|
if (write)
|
|
csr |= DMA_ST_WRITE;
|
|
else
|
|
csr &= ~DMA_ST_WRITE;
|
|
esp->prev_hme_dmacsr = csr;
|
|
|
|
dma_write32(dma_count, DMA_COUNT);
|
|
dma_write32(addr, DMA_ADDR);
|
|
dma_write32(csr, DMA_CSR);
|
|
} else {
|
|
csr = dma_read32(DMA_CSR);
|
|
csr |= DMA_ENABLE;
|
|
if (write)
|
|
csr |= DMA_ST_WRITE;
|
|
else
|
|
csr &= ~DMA_ST_WRITE;
|
|
dma_write32(csr, DMA_CSR);
|
|
if (esp->dmarev == dvmaesc1) {
|
|
u32 end = PAGE_ALIGN(addr + dma_count + 16U);
|
|
dma_write32(end - addr, DMA_COUNT);
|
|
}
|
|
dma_write32(addr, DMA_ADDR);
|
|
|
|
scsi_esp_cmd(esp, cmd);
|
|
}
|
|
|
|
}
|
|
|
|
static int sbus_esp_dma_error(struct esp *esp)
|
|
{
|
|
u32 csr = dma_read32(DMA_CSR);
|
|
|
|
if (csr & DMA_HNDL_ERROR)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct esp_driver_ops sbus_esp_ops = {
|
|
.esp_write8 = sbus_esp_write8,
|
|
.esp_read8 = sbus_esp_read8,
|
|
.irq_pending = sbus_esp_irq_pending,
|
|
.reset_dma = sbus_esp_reset_dma,
|
|
.dma_drain = sbus_esp_dma_drain,
|
|
.dma_invalidate = sbus_esp_dma_invalidate,
|
|
.send_dma_cmd = sbus_esp_send_dma_cmd,
|
|
.dma_error = sbus_esp_dma_error,
|
|
};
|
|
|
|
static int esp_sbus_probe_one(struct platform_device *op,
|
|
struct platform_device *espdma, int hme)
|
|
{
|
|
const struct scsi_host_template *tpnt = &scsi_esp_template;
|
|
struct Scsi_Host *host;
|
|
struct esp *esp;
|
|
int err;
|
|
|
|
host = scsi_host_alloc(tpnt, sizeof(struct esp));
|
|
|
|
err = -ENOMEM;
|
|
if (!host)
|
|
goto fail;
|
|
|
|
host->max_id = (hme ? 16 : 8);
|
|
esp = shost_priv(host);
|
|
|
|
esp->host = host;
|
|
esp->dev = &op->dev;
|
|
esp->ops = &sbus_esp_ops;
|
|
|
|
if (hme)
|
|
esp->flags |= ESP_FLAG_WIDE_CAPABLE;
|
|
|
|
err = esp_sbus_setup_dma(esp, espdma);
|
|
if (err < 0)
|
|
goto fail_unlink;
|
|
|
|
err = esp_sbus_map_regs(esp, hme);
|
|
if (err < 0)
|
|
goto fail_unlink;
|
|
|
|
err = esp_sbus_map_command_block(esp);
|
|
if (err < 0)
|
|
goto fail_unmap_regs;
|
|
|
|
err = esp_sbus_register_irq(esp);
|
|
if (err < 0)
|
|
goto fail_unmap_command_block;
|
|
|
|
esp_sbus_get_props(esp, espdma);
|
|
|
|
/* Before we try to touch the ESP chip, ESC1 dma can
|
|
* come up with the reset bit set, so make sure that
|
|
* is clear first.
|
|
*/
|
|
if (esp->dmarev == dvmaesc1) {
|
|
u32 val = dma_read32(DMA_CSR);
|
|
|
|
dma_write32(val & ~DMA_RST_SCSI, DMA_CSR);
|
|
}
|
|
|
|
dev_set_drvdata(&op->dev, esp);
|
|
|
|
err = scsi_esp_register(esp);
|
|
if (err)
|
|
goto fail_free_irq;
|
|
|
|
return 0;
|
|
|
|
fail_free_irq:
|
|
free_irq(host->irq, esp);
|
|
fail_unmap_command_block:
|
|
dma_free_coherent(&op->dev, 16,
|
|
esp->command_block,
|
|
esp->command_block_dma);
|
|
fail_unmap_regs:
|
|
of_iounmap(&op->resource[(hme ? 1 : 0)], esp->regs, SBUS_ESP_REG_SIZE);
|
|
fail_unlink:
|
|
scsi_host_put(host);
|
|
fail:
|
|
return err;
|
|
}
|
|
|
|
static int esp_sbus_probe(struct platform_device *op)
|
|
{
|
|
struct device_node *dma_node = NULL;
|
|
struct device_node *dp = op->dev.of_node;
|
|
struct platform_device *dma_of = NULL;
|
|
int hme = 0;
|
|
int ret;
|
|
|
|
if (of_node_name_eq(dp->parent, "espdma") ||
|
|
of_node_name_eq(dp->parent, "dma"))
|
|
dma_node = dp->parent;
|
|
else if (of_node_name_eq(dp, "SUNW,fas")) {
|
|
dma_node = op->dev.of_node;
|
|
hme = 1;
|
|
}
|
|
if (dma_node)
|
|
dma_of = of_find_device_by_node(dma_node);
|
|
if (!dma_of)
|
|
return -ENODEV;
|
|
|
|
ret = esp_sbus_probe_one(op, dma_of, hme);
|
|
if (ret)
|
|
put_device(&dma_of->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void esp_sbus_remove(struct platform_device *op)
|
|
{
|
|
struct esp *esp = dev_get_drvdata(&op->dev);
|
|
struct platform_device *dma_of = esp->dma;
|
|
unsigned int irq = esp->host->irq;
|
|
bool is_hme;
|
|
u32 val;
|
|
|
|
scsi_esp_unregister(esp);
|
|
|
|
/* Disable interrupts. */
|
|
val = dma_read32(DMA_CSR);
|
|
dma_write32(val & ~DMA_INT_ENAB, DMA_CSR);
|
|
|
|
free_irq(irq, esp);
|
|
|
|
is_hme = (esp->dmarev == dvmahme);
|
|
|
|
dma_free_coherent(&op->dev, 16,
|
|
esp->command_block,
|
|
esp->command_block_dma);
|
|
of_iounmap(&op->resource[(is_hme ? 1 : 0)], esp->regs,
|
|
SBUS_ESP_REG_SIZE);
|
|
of_iounmap(&dma_of->resource[0], esp->dma_regs,
|
|
resource_size(&dma_of->resource[0]));
|
|
|
|
scsi_host_put(esp->host);
|
|
|
|
dev_set_drvdata(&op->dev, NULL);
|
|
|
|
put_device(&dma_of->dev);
|
|
}
|
|
|
|
static const struct of_device_id esp_match[] = {
|
|
{
|
|
.name = "SUNW,esp",
|
|
},
|
|
{
|
|
.name = "SUNW,fas",
|
|
},
|
|
{
|
|
.name = "esp",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, esp_match);
|
|
|
|
static struct platform_driver esp_sbus_driver = {
|
|
.driver = {
|
|
.name = "esp",
|
|
.of_match_table = esp_match,
|
|
},
|
|
.probe = esp_sbus_probe,
|
|
.remove_new = esp_sbus_remove,
|
|
};
|
|
module_platform_driver(esp_sbus_driver);
|
|
|
|
MODULE_DESCRIPTION("Sun ESP SCSI driver");
|
|
MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(DRV_VERSION);
|