ahci_elphel.c 8.92 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
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
26
#include <linux/sysfs.h>
Mikhail Karpenko's avatar
Mikhail Karpenko committed
27 28 29
#include "ahci.h"

#define DRV_NAME "elphel-ahci"
30 31 32 33 34 35
/*
 * FPGA bitstream control address and bit mask. These are used to check whether
 * bitstream is loaded or not.
 */
#define BITSTREAM_CTRL_ADDR	0xf800700c
#define BITSTREAM_CTRL_BIT	0x4
Mikhail Karpenko's avatar
Mikhail Karpenko committed
36 37 38 39 40 41 42 43 44

/* 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[];
45 46 47
static const struct attribute_group dev_attr_root_group;

static bool load_driver = false;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
48 49 50 51

struct elphel_ahci_priv {
	u32 clb_offs;
	u32 fb_offs;
52
	u32 base_addr;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
53 54
};

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
static ssize_t set_load_flag(struct device *dev, struct device_attribute *attr,
		const char *buff, size_t buff_sz)
{
	load_driver = true;

	return buff_sz;
}

static int bitstream_loaded(u32 *ptr)
{
	u32 val = ioread32(ptr);

	if (val & BITSTREAM_CTRL_BIT)
		return 1;
	else
		return 0;
}

static void elphel_defer_load(struct device *dev)
{
	bool check_flag = true;
	u32 *ctrl_ptr = ioremap_nocache(BITSTREAM_CTRL_ADDR, 4);

	dev_info(dev, "AHCI driver loading is deferred. Load bitstream and write 1 into "
			"/sys/devices/soc0/amba@0/80000000.elphel-ahci/load_module to continue\n");
	while (check_flag) {
		if (load_driver) {
			if (bitstream_loaded(ctrl_ptr)) {
				check_flag = false;
			} else {
				dev_err(dev, "FPGA bitstream is not loaded or bitstream "
						"does not contain AHCI controller\n");
				load_driver = false;
			}
		} else {
			msleep(1000);
		}
	}
	load_driver = false;
	iounmap(ctrl_ptr);
}

97 98
// 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
99 100 101 102 103 104 105 106 107
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;

108
	dev_dbg(dev, "starting port %d", ap->port_no);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
109
	pp = devm_kzalloc(dev, sizeof(struct ahci_port_priv), GFP_KERNEL);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
110 111 112
	if (!pp)
		return -ENOMEM;

113
	mem = devm_kmalloc(dev, 0x100000, GFP_KERNEL); // AHCI_CMD_TBL_AR_SZ = 0x16000
Mikhail Karpenko's avatar
Mikhail Karpenko committed
114 115
	if (!mem)
		return -ENOMEM;
116 117
	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

118 119
	pp->cmd_tbl = mem;
	pp->cmd_tbl_dma = mem_dma;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
120 121 122 123

	/*
	 * Set predefined addresses
	 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
124
	pp->cmd_slot = hpriv->mmio + dpriv->clb_offs;
125
	pp->cmd_slot_dma = dpriv->base_addr + dpriv->clb_offs;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
126 127

	pp->rx_fis = hpriv->mmio + dpriv->fb_offs;
128
	pp->rx_fis_dma = dpriv->base_addr + dpriv->fb_offs;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
129 130 131 132 133

	/*
	 * Save off initial list of interrupts to be enabled.
	 * This could be changed later
	 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
134
	pp->intr_mask = DEF_PORT_IRQ;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
135 136 137

	ap->private_data = pp;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
138
	return ahci_port_resume(ap);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
139 140 141 142 143 144
}

static int elphel_parse_prop(const struct device_node *devn,
		struct device *dev,
		struct elphel_ahci_priv *dpriv)
{
145
	int rc = 0;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
146
	const __be32 *val;
147
	struct resource res;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
148 149

	if (!devn) {
150
		dev_err(dev, "elphel-ahci device tree node is not found");
Mikhail Karpenko's avatar
Mikhail Karpenko committed
151 152 153 154
		return -EINVAL;
	}

	val = of_get_property(devn, PROP_NAME_CLB_OFFS, NULL);
155 156 157 158
	if (!val) {
		dev_err(dev, "can not find clb_offs in device tree");
		return -EINVAL;
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
159
	dpriv->clb_offs = be32_to_cpup(val);
160

Mikhail Karpenko's avatar
Mikhail Karpenko committed
161
	val = of_get_property(devn, PROP_NAME_FB_OFFS, NULL);
162 163 164 165
	if (!val) {
		dev_err(dev, "can not find fb_offs in device tree");
		return -EINVAL;
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
166
	dpriv->fb_offs = be32_to_cpup(val);
167 168 169 170 171

	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;
172
	}
173
	dpriv->base_addr = (u32)res.start;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
174 175 176 177 178 179 180 181

	return 0;
}

static int elphel_drv_probe(struct platform_device *pdev)
{
	int ret;
	struct ahci_host_priv *hpriv;
182
	struct elphel_ahci_priv *dpriv;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
183 184
	struct device *dev = &pdev->dev;
	const struct of_device_id *match;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
185
	unsigned int reg_val;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
186

187 188 189 190 191 192 193
	if (&dev->kobj) {
		ret = sysfs_create_group(&dev->kobj, &dev_attr_root_group);
		if (ret < 0)
			return ret;
	}
	elphel_defer_load(dev);

Mikhail Karpenko's avatar
Mikhail Karpenko committed
194
	dev_info(&pdev->dev, "probing Elphel AHCI driver");
195

196 197
	dpriv = devm_kzalloc(dev, sizeof(struct elphel_ahci_priv), GFP_KERNEL);
	if (!dpriv)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
198 199 200 201 202 203
		return -ENOMEM;

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

204
	ret = elphel_parse_prop(dev->of_node, dev, dpriv);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
205 206 207
	if (ret != 0)
		return ret;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
208
	hpriv = ahci_platform_get_resources(pdev);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
209 210 211
	if (IS_ERR(hpriv))
		return PTR_ERR(hpriv);

212
	hpriv->plat_data = dpriv;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
213

Mikhail Karpenko's avatar
Mikhail Karpenko committed
214
	ret = ahci_platform_init_host(pdev, hpriv, &ahci_elphel_port_info,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
215 216
			&ahci_platform_sht);
	if (ret) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
217
		dev_err(dev, "can not initialize platform host");
Mikhail Karpenko's avatar
Mikhail Karpenko committed
218 219 220 221 222 223 224 225 226 227
		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");
228
	sysfs_remove_group(&pdev->dev.kobj, &dev_attr_root_group);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
229 230 231 232 233
	ata_platform_remove_one(pdev);

	return 0;
}

234
static void elphel_qc_prep(struct ata_queued_cmd *qc)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
235
{
236 237 238 239 240 241 242 243 244 245
	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;

246 247 248
	/* There is only one slot in controller thus we need to change tag*/
	qc->tag = 0;

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	/*
	 * 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);
		}
278
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
279

280 281 282 283 284 285 286 287 288 289 290 291
	/*
	 * 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
292 293
}

294 295 296 297 298 299 300 301 302 303
static DEVICE_ATTR(load_module, S_IWUSR | S_IWGRP, NULL, set_load_flag);
static struct attribute *root_dev_attrs[] = {
		&dev_attr_load_module.attr,
		NULL
};
static const struct attribute_group dev_attr_root_group = {
		.attrs			= root_dev_attrs,
		.name			= NULL,
};

Mikhail Karpenko's avatar
Mikhail Karpenko committed
304 305 306
static struct ata_port_operations ahci_elphel_ops = {
		.inherits		= &ahci_ops,
		.port_start		= elphel_port_start,
307
		.qc_prep		= elphel_qc_prep,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
308 309 310
};

static const struct ata_port_info ahci_elphel_port_info = {
311
		AHCI_HFLAGS(AHCI_HFLAG_NO_NCQ),
Mikhail Karpenko's avatar
Mikhail Karpenko committed
312 313 314 315 316 317 318 319
		.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),
320 321 322 323 324
		.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
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
};

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