t7l66xb.c 10.9 KB
Newer Older
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 26
/*
 *
 * Toshiba T7L66XB core mfd support
 *
 * Copyright (c) 2005, 2007, 2008 Ian Molton
 * Copyright (c) 2008 Dmitry Baryshkov
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * T7L66 features:
 *
 * Supported in this driver:
 * SD/MMC
 * SM/NAND flash controller
 *
 * As yet not supported
 * GPIO interface (on NAND pins)
 * Serial interface
 * TFT 'interface converter'
 * PCMCIA interface logic
 */

#include <linux/kernel.h>
#include <linux/module.h>
27
#include <linux/err.h>
28
#include <linux/io.h>
29
#include <linux/slab.h>
30
#include <linux/irq.h>
31
#include <linux/clk.h>
32 33 34 35 36 37 38 39 40 41
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/mfd/tmio.h>
#include <linux/mfd/t7l66xb.h>

enum {
	T7L66XB_CELL_NAND,
	T7L66XB_CELL_MMC,
};

42 43 44 45 46 47 48 49 50 51 52 53 54
static const struct resource t7l66xb_mmc_resources[] = {
	{
		.start = 0x800,
		.end	= 0x9ff,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = IRQ_T7L66XB_MMC,
		.end	= IRQ_T7L66XB_MMC,
		.flags = IORESOURCE_IRQ,
	},
};

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
#define SCR_REVID	0x08		/* b Revision ID	*/
#define SCR_IMR		0x42		/* b Interrupt Mask	*/
#define SCR_DEV_CTL	0xe0		/* b Device control	*/
#define SCR_ISR		0xe1		/* b Interrupt Status	*/
#define SCR_GPO_OC	0xf0		/* b GPO output control	*/
#define SCR_GPO_OS	0xf1		/* b GPO output enable	*/
#define SCR_GPI_S	0xf2		/* w GPI status		*/
#define SCR_APDC	0xf8		/* b Active pullup down ctrl */

#define SCR_DEV_CTL_USB		BIT(0)	/* USB enable		*/
#define SCR_DEV_CTL_MMC		BIT(1)	/* MMC enable		*/

/*--------------------------------------------------------------------------*/

struct t7l66xb {
	void __iomem		*scr;
	/* Lock to protect registers requiring read/modify/write ops. */
	spinlock_t		lock;

	struct resource		rscr;
75 76
	struct clk		*clk48m;
	struct clk		*clk32k;
77 78 79 80 81 82 83 84 85 86 87 88 89
	int			irq;
	int			irq_base;
};

/*--------------------------------------------------------------------------*/

static int t7l66xb_mmc_enable(struct platform_device *mmc)
{
	struct platform_device *dev = to_platform_device(mmc->dev.parent);
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
	unsigned long flags;
	u8 dev_ctl;

90
	clk_enable(t7l66xb->clk32k);
91 92 93 94 95 96 97 98 99

	spin_lock_irqsave(&t7l66xb->lock, flags);

	dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL);
	dev_ctl |= SCR_DEV_CTL_MMC;
	tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL);

	spin_unlock_irqrestore(&t7l66xb->lock, flags);

100 101 102
	tmio_core_mmc_enable(t7l66xb->scr + 0x200, 0,
		t7l66xb_mmc_resources[0].start & 0xfffe);

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
	return 0;
}

static int t7l66xb_mmc_disable(struct platform_device *mmc)
{
	struct platform_device *dev = to_platform_device(mmc->dev.parent);
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
	unsigned long flags;
	u8 dev_ctl;

	spin_lock_irqsave(&t7l66xb->lock, flags);

	dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL);
	dev_ctl &= ~SCR_DEV_CTL_MMC;
	tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL);

	spin_unlock_irqrestore(&t7l66xb->lock, flags);

121
	clk_disable(t7l66xb->clk32k);
122 123 124 125

	return 0;
}

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
static void t7l66xb_mmc_pwr(struct platform_device *mmc, int state)
{
	struct platform_device *dev = to_platform_device(mmc->dev.parent);
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);

	tmio_core_mmc_pwr(t7l66xb->scr + 0x200, 0, state);
}

static void t7l66xb_mmc_clk_div(struct platform_device *mmc, int state)
{
	struct platform_device *dev = to_platform_device(mmc->dev.parent);
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);

	tmio_core_mmc_clk_div(t7l66xb->scr + 0x200, 0, state);
}

142 143
/*--------------------------------------------------------------------------*/

S
Samuel Ortiz 已提交
144
static struct tmio_mmc_data t7166xb_mmc_data = {
145
	.hclk = 24000000,
146 147
	.set_pwr = t7l66xb_mmc_pwr,
	.set_clk_div = t7l66xb_mmc_clk_div,
148 149
};

150
static const struct resource t7l66xb_nand_resources[] = {
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
	{
		.start	= 0xc00,
		.end	= 0xc07,
		.flags	= IORESOURCE_MEM,
	},
	{
		.start	= 0x0100,
		.end	= 0x01ff,
		.flags	= IORESOURCE_MEM,
	},
	{
		.start	= IRQ_T7L66XB_NAND,
		.end	= IRQ_T7L66XB_NAND,
		.flags	= IORESOURCE_IRQ,
	},
};

static struct mfd_cell t7l66xb_cells[] = {
	[T7L66XB_CELL_MMC] = {
		.name = "tmio-mmc",
		.enable = t7l66xb_mmc_enable,
		.disable = t7l66xb_mmc_disable,
173 174
		.platform_data = &t7166xb_mmc_data,
		.pdata_size    = sizeof(t7166xb_mmc_data),
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
		.num_resources = ARRAY_SIZE(t7l66xb_mmc_resources),
		.resources = t7l66xb_mmc_resources,
	},
	[T7L66XB_CELL_NAND] = {
		.name = "tmio-nand",
		.num_resources = ARRAY_SIZE(t7l66xb_nand_resources),
		.resources = t7l66xb_nand_resources,
	},
};

/*--------------------------------------------------------------------------*/

/* Handle the T7L66XB interrupt mux */
static void t7l66xb_irq(unsigned int irq, struct irq_desc *desc)
{
T
Thomas Gleixner 已提交
190
	struct t7l66xb *t7l66xb = irq_get_handler_data(irq);
191 192 193 194 195 196 197 198 199 200 201 202
	unsigned int isr;
	unsigned int i, irq_base;

	irq_base = t7l66xb->irq_base;

	while ((isr = tmio_ioread8(t7l66xb->scr + SCR_ISR) &
				~tmio_ioread8(t7l66xb->scr + SCR_IMR)))
		for (i = 0; i < T7L66XB_NR_IRQS; i++)
			if (isr & (1 << i))
				generic_handle_irq(irq_base + i);
}

203
static void t7l66xb_irq_mask(struct irq_data *data)
204
{
205
	struct t7l66xb *t7l66xb = irq_data_get_irq_chip_data(data);
206 207 208 209 210
	unsigned long			flags;
	u8 imr;

	spin_lock_irqsave(&t7l66xb->lock, flags);
	imr = tmio_ioread8(t7l66xb->scr + SCR_IMR);
211
	imr |= 1 << (data->irq - t7l66xb->irq_base);
212 213 214 215
	tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR);
	spin_unlock_irqrestore(&t7l66xb->lock, flags);
}

216
static void t7l66xb_irq_unmask(struct irq_data *data)
217
{
218
	struct t7l66xb *t7l66xb = irq_data_get_irq_chip_data(data);
219 220 221 222 223
	unsigned long flags;
	u8 imr;

	spin_lock_irqsave(&t7l66xb->lock, flags);
	imr = tmio_ioread8(t7l66xb->scr + SCR_IMR);
224
	imr &= ~(1 << (data->irq - t7l66xb->irq_base));
225 226 227 228 229
	tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR);
	spin_unlock_irqrestore(&t7l66xb->lock, flags);
}

static struct irq_chip t7l66xb_chip = {
230 231 232 233
	.name		= "t7l66xb",
	.irq_ack	= t7l66xb_irq_mask,
	.irq_mask	= t7l66xb_irq_mask,
	.irq_unmask	= t7l66xb_irq_unmask,
234 235 236 237 238 239 240 241 242 243 244 245 246
};

/*--------------------------------------------------------------------------*/

/* Install the IRQ handler */
static void t7l66xb_attach_irq(struct platform_device *dev)
{
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
	unsigned int irq, irq_base;

	irq_base = t7l66xb->irq_base;

	for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) {
247
		irq_set_chip_and_handler(irq, &t7l66xb_chip, handle_level_irq);
T
Thomas Gleixner 已提交
248
		irq_set_chip_data(irq, t7l66xb);
249 250 251 252 253
#ifdef CONFIG_ARM
		set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
#endif
	}

T
Thomas Gleixner 已提交
254 255 256
	irq_set_irq_type(t7l66xb->irq, IRQ_TYPE_EDGE_FALLING);
	irq_set_handler_data(t7l66xb->irq, t7l66xb);
	irq_set_chained_handler(t7l66xb->irq, t7l66xb_irq);
257 258 259 260 261 262 263 264 265
}

static void t7l66xb_detach_irq(struct platform_device *dev)
{
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
	unsigned int irq, irq_base;

	irq_base = t7l66xb->irq_base;

T
Thomas Gleixner 已提交
266 267
	irq_set_chained_handler(t7l66xb->irq, NULL);
	irq_set_handler_data(t7l66xb->irq, NULL);
268 269 270 271 272

	for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) {
#ifdef CONFIG_ARM
		set_irq_flags(irq, 0);
#endif
T
Thomas Gleixner 已提交
273 274
		irq_set_chip(irq, NULL);
		irq_set_chip_data(irq, NULL);
275 276 277 278 279 280 281 282
	}
}

/*--------------------------------------------------------------------------*/

#ifdef CONFIG_PM
static int t7l66xb_suspend(struct platform_device *dev, pm_message_t state)
{
283
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
284 285 286 287
	struct t7l66xb_platform_data *pdata = dev->dev.platform_data;

	if (pdata && pdata->suspend)
		pdata->suspend(dev);
288
	clk_disable(t7l66xb->clk48m);
289 290 291 292 293 294

	return 0;
}

static int t7l66xb_resume(struct platform_device *dev)
{
295
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
296 297
	struct t7l66xb_platform_data *pdata = dev->dev.platform_data;

298
	clk_enable(t7l66xb->clk48m);
299 300 301
	if (pdata && pdata->resume)
		pdata->resume(dev);

302 303 304
	tmio_core_mmc_enable(t7l66xb->scr + 0x200, 0,
		t7l66xb_mmc_resources[0].start & 0xfffe);

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	return 0;
}
#else
#define t7l66xb_suspend NULL
#define t7l66xb_resume	NULL
#endif

/*--------------------------------------------------------------------------*/

static int t7l66xb_probe(struct platform_device *dev)
{
	struct t7l66xb_platform_data *pdata = dev->dev.platform_data;
	struct t7l66xb *t7l66xb;
	struct resource *iomem, *rscr;
	int ret;

321 322 323
	if (pdata == NULL)
		return -EINVAL;

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
	iomem = platform_get_resource(dev, IORESOURCE_MEM, 0);
	if (!iomem)
		return -EINVAL;

	t7l66xb = kzalloc(sizeof *t7l66xb, GFP_KERNEL);
	if (!t7l66xb)
		return -ENOMEM;

	spin_lock_init(&t7l66xb->lock);

	platform_set_drvdata(dev, t7l66xb);

	ret = platform_get_irq(dev, 0);
	if (ret >= 0)
		t7l66xb->irq = ret;
	else
		goto err_noirq;

	t7l66xb->irq_base = pdata->irq_base;

344 345 346 347 348 349 350 351 352 353 354 355
	t7l66xb->clk32k = clk_get(&dev->dev, "CLK_CK32K");
	if (IS_ERR(t7l66xb->clk32k)) {
		ret = PTR_ERR(t7l66xb->clk32k);
		goto err_clk32k_get;
	}

	t7l66xb->clk48m = clk_get(&dev->dev, "CLK_CK48M");
	if (IS_ERR(t7l66xb->clk48m)) {
		ret = PTR_ERR(t7l66xb->clk48m);
		goto err_clk48m_get;
	}

356 357 358 359 360 361 362 363 364 365
	rscr = &t7l66xb->rscr;
	rscr->name = "t7l66xb-core";
	rscr->start = iomem->start;
	rscr->end = iomem->start + 0xff;
	rscr->flags = IORESOURCE_MEM;

	ret = request_resource(iomem, rscr);
	if (ret)
		goto err_request_scr;

366
	t7l66xb->scr = ioremap(rscr->start, resource_size(rscr));
367 368 369 370 371
	if (!t7l66xb->scr) {
		ret = -ENOMEM;
		goto err_ioremap;
	}

372 373
	clk_enable(t7l66xb->clk48m);

374 375 376 377 378 379 380 381 382 383 384 385
	if (pdata && pdata->enable)
		pdata->enable(dev);

	/* Mask all interrupts */
	tmio_iowrite8(0xbf, t7l66xb->scr + SCR_IMR);

	printk(KERN_INFO "%s rev %d @ 0x%08lx, irq %d\n",
		dev->name, tmio_ioread8(t7l66xb->scr + SCR_REVID),
		(unsigned long)iomem->start, t7l66xb->irq);

	t7l66xb_attach_irq(dev);

386 387
	t7l66xb_cells[T7L66XB_CELL_NAND].platform_data = pdata->nand_data;
	t7l66xb_cells[T7L66XB_CELL_NAND].pdata_size = sizeof(*pdata->nand_data);
I
Ian Molton 已提交
388

389 390
	ret = mfd_add_devices(&dev->dev, dev->id,
			      t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells),
391
			      iomem, t7l66xb->irq_base, NULL);
392 393 394 395 396 397 398 399 400

	if (!ret)
		return 0;

	t7l66xb_detach_irq(dev);
	iounmap(t7l66xb->scr);
err_ioremap:
	release_resource(&t7l66xb->rscr);
err_request_scr:
401 402 403 404 405
	clk_put(t7l66xb->clk48m);
err_clk48m_get:
	clk_put(t7l66xb->clk32k);
err_clk32k_get:
err_noirq:
406
	kfree(t7l66xb);
407 408 409 410 411 412 413 414 415 416
	return ret;
}

static int t7l66xb_remove(struct platform_device *dev)
{
	struct t7l66xb_platform_data *pdata = dev->dev.platform_data;
	struct t7l66xb *t7l66xb = platform_get_drvdata(dev);
	int ret;

	ret = pdata->disable(dev);
417 418
	clk_disable(t7l66xb->clk48m);
	clk_put(t7l66xb->clk48m);
419 420
	clk_disable(t7l66xb->clk32k);
	clk_put(t7l66xb->clk32k);
421 422 423
	t7l66xb_detach_irq(dev);
	iounmap(t7l66xb->scr);
	release_resource(&t7l66xb->rscr);
424
	mfd_remove_devices(&dev->dev);
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
	platform_set_drvdata(dev, NULL);
	kfree(t7l66xb);

	return ret;

}

static struct platform_driver t7l66xb_platform_driver = {
	.driver = {
		.name	= "t7l66xb",
		.owner	= THIS_MODULE,
	},
	.suspend	= t7l66xb_suspend,
	.resume		= t7l66xb_resume,
	.probe		= t7l66xb_probe,
	.remove		= t7l66xb_remove,
};

/*--------------------------------------------------------------------------*/

445
module_platform_driver(t7l66xb_platform_driver);
446 447 448 449 450

MODULE_DESCRIPTION("Toshiba T7L66XB core driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Ian Molton");
MODULE_ALIAS("platform:t7l66xb");