ahci_elphel.c 7.26 KB
Newer Older
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * Elphel AHCI SATA platform driver for elphel393 camera
 *
 * Based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 */

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ahci_platform.h>
#include <linux/of.h>
#include <linux/of_device.h>
22
#include <linux/of_address.h>
Mikhail Karpenko's avatar
Mikhail Karpenko committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include "ahci.h"

#define DRV_NAME "elphel-ahci"

/* Property names from device tree, these are specific for the controller */
#define PROP_NAME_CLB_OFFS "clb_offs"
#define PROP_NAME_FB_OFFS "fb_offs"

static struct ata_port_operations ahci_elphel_ops;
static const struct ata_port_info ahci_elphel_port_info;
static struct scsi_host_template ahci_platform_sht;
static const struct of_device_id ahci_elphel_of_match[];

struct elphel_ahci_priv {
	u32 clb_offs;
	u32 fb_offs;
42
	u32 base_addr;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
43 44
};

45 46
// What about port_stop and freeing/unmapping ?
// Or at least check if it is re-started and memory is already allocated/mapped
Mikhail Karpenko's avatar
Mikhail Karpenko committed
47 48 49 50 51 52 53 54 55
static int elphel_port_start(struct ata_port *ap)
{
	void *mem;
	dma_addr_t mem_dma;
	struct device *dev = ap->host->dev;
	struct ahci_port_priv *pp;
	struct ahci_host_priv *hpriv = ap->host->private_data;
	const struct elphel_ahci_priv *dpriv = hpriv->plat_data;

56
	dev_dbg(dev, "starting port %d", ap->port_no);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
57
	pp = devm_kzalloc(dev, sizeof(struct ahci_port_priv), GFP_KERNEL);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
58 59 60
	if (!pp)
		return -ENOMEM;

61
	mem = devm_kmalloc(dev, 0x100000, GFP_KERNEL); // AHCI_CMD_TBL_AR_SZ = 0x16000
Mikhail Karpenko's avatar
Mikhail Karpenko committed
62 63
	if (!mem)
		return -ENOMEM;
64 65
	mem_dma = dma_map_single(dev, mem, AHCI_CMD_TBL_AR_SZ, DMA_TO_DEVICE); // maybe DMA_BIDIRECTIONAL, but currently we do not use DMA for received FISes

66 67
	pp->cmd_tbl = mem;
	pp->cmd_tbl_dma = mem_dma;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
68 69 70 71

	/*
	 * Set predefined addresses
	 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
72
	pp->cmd_slot = hpriv->mmio + dpriv->clb_offs;
73
	pp->cmd_slot_dma = dpriv->base_addr + dpriv->clb_offs;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
74 75

	pp->rx_fis = hpriv->mmio + dpriv->fb_offs;
76
	pp->rx_fis_dma = dpriv->base_addr + dpriv->fb_offs;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
77 78 79 80 81

	/*
	 * Save off initial list of interrupts to be enabled.
	 * This could be changed later
	 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
82
	pp->intr_mask = DEF_PORT_IRQ;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
83 84 85

	ap->private_data = pp;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
86
	return ahci_port_resume(ap);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
87 88 89 90 91 92
}

static int elphel_parse_prop(const struct device_node *devn,
		struct device *dev,
		struct elphel_ahci_priv *dpriv)
{
93
	int rc = 0;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
94
	const __be32 *val;
95
	struct resource res;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
96 97

	if (!devn) {
98
		dev_err(dev, "elphel-ahci device tree node is not found");
Mikhail Karpenko's avatar
Mikhail Karpenko committed
99 100 101 102
		return -EINVAL;
	}

	val = of_get_property(devn, PROP_NAME_CLB_OFFS, NULL);
103 104 105 106
	if (!val) {
		dev_err(dev, "can not find clb_offs in device tree");
		return -EINVAL;
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
107
	dpriv->clb_offs = be32_to_cpup(val);
108

Mikhail Karpenko's avatar
Mikhail Karpenko committed
109
	val = of_get_property(devn, PROP_NAME_FB_OFFS, NULL);
110 111 112 113
	if (!val) {
		dev_err(dev, "can not find fb_offs in device tree");
		return -EINVAL;
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
114
	dpriv->fb_offs = be32_to_cpup(val);
115 116 117 118 119

	rc = of_address_to_resource((struct device_node *)devn, 0, &res);
	if (rc < 0) {
		dev_err(dev, "can not find address in device tree");
		return -EINVAL;
120
	}
121
	dpriv->base_addr = (u32)res.start;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
122 123 124 125 126 127 128 129

	return 0;
}

static int elphel_drv_probe(struct platform_device *pdev)
{
	int ret;
	struct ahci_host_priv *hpriv;
130
	struct elphel_ahci_priv *dpriv;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
131 132
	struct device *dev = &pdev->dev;
	const struct of_device_id *match;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
133
	unsigned int reg_val;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
134 135

	dev_info(&pdev->dev, "probing Elphel AHCI driver");
136

137 138
	dpriv = devm_kzalloc(dev, sizeof(struct elphel_ahci_priv), GFP_KERNEL);
	if (!dpriv)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
139 140 141 142 143 144
		return -ENOMEM;

	match = of_match_device(ahci_elphel_of_match, &pdev->dev);
	if (!match)
		return -EINVAL;

145
	ret = elphel_parse_prop(dev->of_node, dev, dpriv);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
146 147 148
	if (ret != 0)
		return ret;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
149
	hpriv = ahci_platform_get_resources(pdev);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
150 151 152
	if (IS_ERR(hpriv))
		return PTR_ERR(hpriv);

153
	hpriv->plat_data = dpriv;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
154

Mikhail Karpenko's avatar
Mikhail Karpenko committed
155
	ret = ahci_platform_init_host(pdev, hpriv, &ahci_elphel_port_info,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
156 157
			&ahci_platform_sht);
	if (ret) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
158
		dev_err(dev, "can not initialize platform host");
Mikhail Karpenko's avatar
Mikhail Karpenko committed
159 160 161 162 163 164 165 166 167 168
		ahci_platform_disable_resources(hpriv);
		return ret;
	}

	return 0;
}

static int elphel_drv_remove(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "removing Elphel AHCI driver");
Mikhail Karpenko's avatar
Mikhail Karpenko committed
169 170 171 172 173
	ata_platform_remove_one(pdev);

	return 0;
}

174
static void elphel_qc_prep(struct ata_queued_cmd *qc)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
175
{
176 177 178 179 180 181 182 183 184 185
	struct ata_port *ap = qc->ap;
	struct ahci_port_priv *pp = ap->private_data;
	int is_atapi = ata_is_atapi(qc->tf.protocol);
	void *cmd_tbl;
	u32 opts;
	const u32 cmd_fis_len = 5; /* five dwords */
	unsigned int n_elem;
	struct scatterlist *sg;
	struct ahci_sg *ahci_sg;

186 187 188
	/* There is only one slot in controller thus we need to change tag*/
	qc->tag = 0;

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	/*
	 * Fill in command table information.  First, the header,
	 * a SATA Register - Host to Device command FIS.
	 */
	dma_sync_single_for_cpu(&qc->dev->tdev, pp->cmd_tbl_dma,
			AHCI_CMD_TBL_AR_SZ, DMA_TO_DEVICE);
	cmd_tbl = pp->cmd_tbl + qc->tag * AHCI_CMD_TBL_SZ;

	ata_tf_to_fis(&qc->tf, qc->dev->link->pmp, 1, cmd_tbl);

	if (is_atapi) {
		memset(cmd_tbl + AHCI_CMD_TBL_CDB, 0, 32);
		memcpy(cmd_tbl + AHCI_CMD_TBL_CDB, qc->cdb, qc->dev->cdb_len);
	}

	/*
	 * Next, the S/G list.
	 */
	n_elem = 0;
	ahci_sg = cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
	if (qc->flags & ATA_QCFLAG_DMAMAP) {
		for_each_sg(qc->sg, sg, qc->n_elem, n_elem) {
			dma_addr_t addr = sg_dma_address(sg);
			u32 sg_len = sg_dma_len(sg);

			ahci_sg[n_elem].addr = cpu_to_le32(addr & 0xffffffff);
			ahci_sg[n_elem].addr_hi = cpu_to_le32((addr >> 16) >> 16);
			ahci_sg[n_elem].flags_size = cpu_to_le32(sg_len - 1);
		}
218
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
219

220 221 222 223 224 225 226 227 228 229 230 231
	/*
	 * Fill in command slot information.
	 */
	opts = cmd_fis_len | n_elem << 16 | (qc->dev->link->pmp << 12);
	if (qc->tf.flags & ATA_TFLAG_WRITE)
		opts |= AHCI_CMD_WRITE;
	if (is_atapi)
		opts |= AHCI_CMD_ATAPI | AHCI_CMD_PREFETCH;

	ahci_fill_cmd_slot(pp, qc->tag, opts);
	dma_sync_single_for_device(&qc->dev->tdev, pp->cmd_tbl_dma,
			AHCI_CMD_TBL_AR_SZ, DMA_TO_DEVICE);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
232 233 234 235 236
}

static struct ata_port_operations ahci_elphel_ops = {
		.inherits		= &ahci_ops,
		.port_start		= elphel_port_start,
237
		.qc_prep		= elphel_qc_prep,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
238 239 240
};

static const struct ata_port_info ahci_elphel_port_info = {
241
		AHCI_HFLAGS(AHCI_HFLAG_NO_NCQ),
Mikhail Karpenko's avatar
Mikhail Karpenko committed
242 243 244 245 246 247 248 249
		.flags			= AHCI_FLAG_COMMON,
		.pio_mask		= ATA_PIO4,
		.udma_mask		= ATA_UDMA6,
		.port_ops		= &ahci_elphel_ops,
};

static struct scsi_host_template ahci_platform_sht = {
		AHCI_SHT(DRV_NAME),
250 251 252 253 254
		.can_queue		= 1,
		.sg_tablesize	= AHCI_MAX_SG,
		.dma_boundary	= AHCI_DMA_BOUNDARY,
		.shost_attrs	= ahci_shost_attrs,
		.sdev_attrs		= ahci_sdev_attrs,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
};

static const struct of_device_id ahci_elphel_of_match[] = {
		{ .compatible = "elphel,elphel-ahci", },
		{ /* end of list */ }
};
MODULE_DEVICE_TABLE(of, ahci_elphel_of_match);

static struct platform_driver ahci_elphel_driver = {
		.probe			= elphel_drv_probe,
		.remove			= elphel_drv_remove,
		.driver	= {
				.name	= DRV_NAME,
				.owner	= THIS_MODULE,
				.of_match_table	= ahci_elphel_of_match,
		},
};
module_platform_driver(ahci_elphel_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Elphel, Inc.");
MODULE_DESCRIPTION("Elphel AHCI SATA platform driver for elphel393 camera");