ahci_elphel.c 5.91 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 22 23 24 25
/*
 * 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>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include "ahci.h"
Mikhail Karpenko's avatar
Mikhail Karpenko committed
26
#include "libahci_debug.h"
Mikhail Karpenko's avatar
Mikhail Karpenko committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

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

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;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
53 54
	libahci_debug_init(ap->host);

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

	mem = dmam_alloc_coherent(dev, AHCI_CMD_TBL_AR_SZ, &mem_dma, GFP_KERNEL);
	if (!mem)
		return -ENOMEM;
	memset(mem, 0, AHCI_CMD_TBL_AR_SZ);
	pp->cmd_tbl = mem;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
65
	pp->cmd_tbl_dma = mem_dma;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
66 67 68 69

	/*
	 * Set predefined addresses
	 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
70
	pp->cmd_slot = hpriv->mmio + dpriv->clb_offs;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
71 72 73
	pp->cmd_slot_dma = virt_to_phys(pp->cmd_slot);

	pp->rx_fis = hpriv->mmio + dpriv->fb_offs;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
74 75 76 77 78 79
	pp->rx_fis_dma = virt_to_phys(pp->rx_fis);

	dev_info(dev, "cmd_slot and rx_fis addresses are set");
	dev_info(dev, "\tmmio address: 0x%p", hpriv->mmio);
	dev_info(dev, "\tcommand slot virtual address: 0x%p", pp->cmd_slot);
	dev_info(dev, "\rx fis virtual address: 0x%p", pp->rx_fis);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
80 81 82 83 84

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

	ap->private_data = pp;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
89
	return ahci_port_resume(ap);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
}

static int elphel_parse_prop(const struct device_node *devn,
		struct device *dev,
		struct elphel_ahci_priv *dpriv)
{
	const __be32 *val;

	if (!devn) {
		dev_err(dev, "device tree node is not found");
		return -EINVAL;
	}

	val = of_get_property(devn, PROP_NAME_CLB_OFFS, NULL);
	dpriv->clb_offs = be32_to_cpup(val);
	val = of_get_property(devn, PROP_NAME_FB_OFFS, NULL);
	dpriv->fb_offs = be32_to_cpup(val);

	return 0;
}

static int elphel_drv_probe(struct platform_device *pdev)
{
	int ret;
	struct ahci_host_priv *hpriv;
	struct elphel_ahci_priv *drv_priv;
	struct device *dev = &pdev->dev;
	const struct of_device_id *match;

	struct resource *res;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
120
	unsigned int reg_val;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134

	dev_info(&pdev->dev, "probing Elphel AHCI driver");
	drv_priv = devm_kzalloc(dev, sizeof(struct elphel_ahci_priv), GFP_KERNEL);
	if (!drv_priv)
		return -ENOMEM;

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

	ret = elphel_parse_prop(dev->of_node, dev, drv_priv);
	if (ret != 0)
		return ret;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
135
	hpriv = ahci_platform_get_resources(pdev);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
136 137 138
	if (IS_ERR(hpriv))
		return PTR_ERR(hpriv);

Mikhail Karpenko's avatar
Mikhail Karpenko committed
139
	hpriv->plat_data = drv_priv;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
140

Mikhail Karpenko's avatar
Mikhail Karpenko committed
141 142 143 144 145 146 147 148 149 150 151 152
	reg_val = readl(hpriv->mmio + HOST_CAP);
	dev_info(dev, "HOST CAP register: 0x%08x", reg_val);
	reg_val = readl(hpriv->mmio + HOST_CTL);
	dev_info(dev, "HOST GHC register: 0x%08x", reg_val);
	reg_val = readl(hpriv->mmio + HOST_IRQ_STAT);
	dev_info(dev, "HOST IS register: 0x%08x", reg_val);
	reg_val = readl(hpriv->mmio + HOST_PORTS_IMPL);
	dev_info(dev, "HOST PI register: 0x%08x", reg_val);
	reg_val = readl(hpriv->mmio + HOST_VERSION);
	dev_info(dev, "HOST VS register: 0x%08x", reg_val);

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

	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
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	ata_platform_remove_one(pdev);
	libahci_debug_exit();

	return 0;
}

static unsigned int elphel_read_id(struct ata_device *dev, struct ata_taskfile *tf, u16 *id)
{
	u32 err_mask;
	struct device *d = &dev->tdev;

	err_mask = ata_do_dev_read_id(dev, tf, id);
	if (err_mask)
		return err_mask;

	dev_info(d, "issue identify command");

	return 0;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
185 186 187 188 189
}

static struct ata_port_operations ahci_elphel_ops = {
		.inherits		= &ahci_ops,
		.port_start		= elphel_port_start,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
190
		.read_id		= elphel_read_id,
Mikhail Karpenko's avatar
Mikhail Karpenko committed
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 218 219 220 221 222 223 224
};

static const struct ata_port_info ahci_elphel_port_info = {
		.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),
};

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			= ata_platform_remove_one,*/
		.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");