mcpdm.c 10.6 KB
Newer Older
1
/*
2
 * mcpdm.c  --	McPDM interface driver
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * Author: Jorge Eduardo Candelaria <x0107209@ti.com>
 * Copyright (C) 2009 - Texas Instruments, Inc.
 *
 * 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.
 *
 * This program is distributed in the hope that 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/wait.h>
28
#include <linux/slab.h>
29 30 31 32 33 34 35 36 37 38 39 40 41
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/irq.h>

#include "mcpdm.h"

static struct omap_mcpdm *mcpdm;

static inline void omap_mcpdm_write(u16 reg, u32 val)
{
42
	__raw_writel(val, mcpdm->io_base + reg);
43 44 45 46
}

static inline int omap_mcpdm_read(u16 reg)
{
47
	return __raw_readl(mcpdm->io_base + reg);
48 49 50 51
}

static void omap_mcpdm_reg_dump(void)
{
52 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
	dev_dbg(mcpdm->dev, "***********************\n");
	dev_dbg(mcpdm->dev, "IRQSTATUS_RAW:  0x%04x\n",
			omap_mcpdm_read(MCPDM_IRQSTATUS_RAW));
	dev_dbg(mcpdm->dev, "IRQSTATUS:	0x%04x\n",
			omap_mcpdm_read(MCPDM_IRQSTATUS));
	dev_dbg(mcpdm->dev, "IRQENABLE_SET:  0x%04x\n",
			omap_mcpdm_read(MCPDM_IRQENABLE_SET));
	dev_dbg(mcpdm->dev, "IRQENABLE_CLR:  0x%04x\n",
			omap_mcpdm_read(MCPDM_IRQENABLE_CLR));
	dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n",
			omap_mcpdm_read(MCPDM_IRQWAKE_EN));
	dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n",
			omap_mcpdm_read(MCPDM_DMAENABLE_SET));
	dev_dbg(mcpdm->dev, "DMAENABLE_CLR:  0x%04x\n",
			omap_mcpdm_read(MCPDM_DMAENABLE_CLR));
	dev_dbg(mcpdm->dev, "DMAWAKEEN:	0x%04x\n",
			omap_mcpdm_read(MCPDM_DMAWAKEEN));
	dev_dbg(mcpdm->dev, "CTRL:	0x%04x\n",
			omap_mcpdm_read(MCPDM_CTRL));
	dev_dbg(mcpdm->dev, "DN_DATA:  0x%04x\n",
			omap_mcpdm_read(MCPDM_DN_DATA));
	dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n",
			omap_mcpdm_read(MCPDM_UP_DATA));
	dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n",
			omap_mcpdm_read(MCPDM_FIFO_CTRL_DN));
	dev_dbg(mcpdm->dev, "FIFO_CTRL_UP:  0x%04x\n",
			omap_mcpdm_read(MCPDM_FIFO_CTRL_UP));
	dev_dbg(mcpdm->dev, "DN_OFFSET:	0x%04x\n",
			omap_mcpdm_read(MCPDM_DN_OFFSET));
	dev_dbg(mcpdm->dev, "***********************\n");
82 83 84 85 86 87 88 89
}

/*
 * Takes the McPDM module in and out of reset state.
 * Uplink and downlink can be reset individually.
 */
static void omap_mcpdm_reset_capture(int reset)
{
90
	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
91

92 93 94 95
	if (reset)
		ctrl |= SW_UP_RST;
	else
		ctrl &= ~SW_UP_RST;
96

97
	omap_mcpdm_write(MCPDM_CTRL, ctrl);
98 99 100 101
}

static void omap_mcpdm_reset_playback(int reset)
{
102
	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
103

104 105 106 107
	if (reset)
		ctrl |= SW_DN_RST;
	else
		ctrl &= ~SW_DN_RST;
108

109
	omap_mcpdm_write(MCPDM_CTRL, ctrl);
110 111 112 113 114 115 116 117
}

/*
 * Enables the transfer through the PDM interface to/from the Phoenix
 * codec by enabling the corresponding UP or DN channels.
 */
void omap_mcpdm_start(int stream)
{
118
	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
119

120 121 122 123
	if (stream)
		ctrl |= mcpdm->up_channels;
	else
		ctrl |= mcpdm->dn_channels;
124

125
	omap_mcpdm_write(MCPDM_CTRL, ctrl);
126 127 128 129 130 131 132 133
}

/*
 * Disables the transfer through the PDM interface to/from the Phoenix
 * codec by disabling the corresponding UP or DN channels.
 */
void omap_mcpdm_stop(int stream)
{
134
	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
135

136 137 138 139
	if (stream)
		ctrl &= ~mcpdm->up_channels;
	else
		ctrl &= ~mcpdm->dn_channels;
140

141
	omap_mcpdm_write(MCPDM_CTRL, ctrl);
142 143 144 145 146 147 148 149
}

/*
 * Configures McPDM uplink for audio recording.
 * This function should be called before omap_mcpdm_start.
 */
int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink)
{
150 151
	int irq_mask = 0;
	int ctrl;
152

153 154
	if (!uplink)
		return -EINVAL;
155

156
	mcpdm->uplink = uplink;
157

158 159 160
	/* Enable irq request generation */
	irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
	omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
161

162 163 164
	/* Configure uplink threshold */
	if (uplink->threshold > UP_THRES_MAX)
		uplink->threshold = UP_THRES_MAX;
165

166
	omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold);
167

168 169
	/* Configure DMA controller */
	omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE);
170

171 172 173 174
	/* Set pdm out format */
	ctrl = omap_mcpdm_read(MCPDM_CTRL);
	ctrl &= ~PDMOUTFORMAT;
	ctrl |= uplink->format & PDMOUTFORMAT;
175

176 177
	/* Uplink channels */
	mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK);
178

179
	omap_mcpdm_write(MCPDM_CTRL, ctrl);
180

181
	return 0;
182 183 184 185 186 187 188 189
}

/*
 * Configures McPDM downlink for audio playback.
 * This function should be called before omap_mcpdm_start.
 */
int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink)
{
190 191
	int irq_mask = 0;
	int ctrl;
192

193 194
	if (!downlink)
		return -EINVAL;
195

196
	mcpdm->downlink = downlink;
197

198 199 200
	/* Enable irq request generation */
	irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
	omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
201

202 203 204
	/* Configure uplink threshold */
	if (downlink->threshold > DN_THRES_MAX)
		downlink->threshold = DN_THRES_MAX;
205

206
	omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold);
207

208 209
	/* Enable DMA request generation */
	omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE);
210

211 212 213 214
	/* Set pdm out format */
	ctrl = omap_mcpdm_read(MCPDM_CTRL);
	ctrl &= ~PDMOUTFORMAT;
	ctrl |= downlink->format & PDMOUTFORMAT;
215

216 217
	/* Downlink channels */
	mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK);
218

219
	omap_mcpdm_write(MCPDM_CTRL, ctrl);
220

221
	return 0;
222 223 224 225 226 227 228 229
}

/*
 * Cleans McPDM uplink configuration.
 * This function should be called when the stream is closed.
 */
int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink)
{
230
	int irq_mask = 0;
231

232 233
	if (!uplink)
		return -EINVAL;
234

235 236 237
	/* Disable irq request generation */
	irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
	omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
238

239 240
	/* Disable DMA request generation */
	omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE);
241

242 243
	/* Clear Downlink channels */
	mcpdm->up_channels = 0;
244

245
	mcpdm->uplink = NULL;
246

247
	return 0;
248 249 250 251 252 253 254 255
}

/*
 * Cleans McPDM downlink configuration.
 * This function should be called when the stream is closed.
 */
int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink)
{
256
	int irq_mask = 0;
257

258 259
	if (!downlink)
		return -EINVAL;
260

261 262 263
	/* Disable irq request generation */
	irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
	omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
264

265 266
	/* Disable DMA request generation */
	omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE);
267

268 269
	/* clear Downlink channels */
	mcpdm->dn_channels = 0;
270

271
	mcpdm->downlink = NULL;
272

273
	return 0;
274 275 276 277
}

static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id)
{
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
	struct omap_mcpdm *mcpdm_irq = dev_id;
	int irq_status;

	irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS);

	/* Acknowledge irq event */
	omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status);

	if (irq & MCPDM_DN_IRQ_FULL) {
		dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
		omap_mcpdm_reset_playback(1);
		omap_mcpdm_playback_open(mcpdm_irq->downlink);
		omap_mcpdm_reset_playback(0);
	}

	if (irq & MCPDM_DN_IRQ_EMPTY) {
		dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
		omap_mcpdm_reset_playback(1);
		omap_mcpdm_playback_open(mcpdm_irq->downlink);
		omap_mcpdm_reset_playback(0);
	}

	if (irq & MCPDM_DN_IRQ) {
		dev_dbg(mcpdm_irq->dev, "DN write request\n");
	}

	if (irq & MCPDM_UP_IRQ_FULL) {
		dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
		omap_mcpdm_reset_capture(1);
		omap_mcpdm_capture_open(mcpdm_irq->uplink);
		omap_mcpdm_reset_capture(0);
	}

	if (irq & MCPDM_UP_IRQ_EMPTY) {
		dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
		omap_mcpdm_reset_capture(1);
		omap_mcpdm_capture_open(mcpdm_irq->uplink);
		omap_mcpdm_reset_capture(0);
	}

	if (irq & MCPDM_UP_IRQ) {
		dev_dbg(mcpdm_irq->dev, "UP write request\n");
	}

	return IRQ_HANDLED;
323 324 325 326
}

int omap_mcpdm_request(void)
{
327
	int ret;
328

329
	clk_enable(mcpdm->clk);
330

331
	spin_lock(&mcpdm->lock);
332

333 334 335 336 337 338 339
	if (!mcpdm->free) {
		dev_err(mcpdm->dev, "McPDM interface is in use\n");
		spin_unlock(&mcpdm->lock);
		ret = -EBUSY;
		goto err;
	}
	mcpdm->free = 0;
340

341
	spin_unlock(&mcpdm->lock);
342

343 344
	/* Disable lines while request is ongoing */
	omap_mcpdm_write(MCPDM_CTRL, 0x00);
345

346 347 348 349 350 351
	ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler,
				0, "McPDM", (void *)mcpdm);
	if (ret) {
		dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n");
		goto err;
	}
352

353
	return 0;
354 355

err:
356 357
	clk_disable(mcpdm->clk);
	return ret;
358 359 360 361
}

void omap_mcpdm_free(void)
{
362 363 364 365 366 367 368 369 370 371 372 373
	spin_lock(&mcpdm->lock);
	if (mcpdm->free) {
		dev_err(mcpdm->dev, "McPDM interface is already free\n");
		spin_unlock(&mcpdm->lock);
		return;
	}
	mcpdm->free = 1;
	spin_unlock(&mcpdm->lock);

	clk_disable(mcpdm->clk);

	free_irq(mcpdm->irq, (void *)mcpdm);
374 375 376 377 378 379 380
}

/* Enable/disable DC offset cancelation for the analog
 * headset path (PDM channels 1 and 2).
 */
int omap_mcpdm_set_offset(int offset1, int offset2)
{
381
	int offset;
382

383 384
	if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX))
		return -EINVAL;
385

386
	offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2);
387

388 389 390 391 392
	/* offset cancellation for channel 1 */
	if (offset1)
		offset |= DN_OFST_RX1_EN;
	else
		offset &= ~DN_OFST_RX1_EN;
393

394 395 396 397 398
	/* offset cancellation for channel 2 */
	if (offset2)
		offset |= DN_OFST_RX2_EN;
	else
		offset &= ~DN_OFST_RX2_EN;
399

400
	omap_mcpdm_write(MCPDM_DN_OFFSET, offset);
401

402
	return 0;
403 404
}

405
int __devinit omap_mcpdm_probe(struct platform_device *pdev)
406
{
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
	struct resource *res;
	int ret = 0;

	mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL);
	if (!mcpdm) {
		ret = -ENOMEM;
		goto exit;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "no resource\n");
		goto err_resource;
	}

	spin_lock_init(&mcpdm->lock);
	mcpdm->free = 1;
	mcpdm->io_base = ioremap(res->start, resource_size(res));
	if (!mcpdm->io_base) {
		ret = -ENOMEM;
		goto err_resource;
	}

	mcpdm->irq = platform_get_irq(pdev, 0);

	mcpdm->clk = clk_get(&pdev->dev, "pdm_ck");
	if (IS_ERR(mcpdm->clk)) {
		ret = PTR_ERR(mcpdm->clk);
		dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret);
		goto err_clk;
	}

	mcpdm->dev = &pdev->dev;
	platform_set_drvdata(pdev, mcpdm);

	return 0;
443 444

err_clk:
445
	iounmap(mcpdm->io_base);
446
err_resource:
447
	kfree(mcpdm);
448
exit:
449
	return ret;
450 451
}

452
int omap_mcpdm_remove(struct platform_device *pdev)
453
{
454
	struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev);
455

456
	platform_set_drvdata(pdev, NULL);
457

458
	clk_put(mcpdm_ptr->clk);
459

460
	iounmap(mcpdm_ptr->io_base);
461

462 463 464
	mcpdm_ptr->clk = NULL;
	mcpdm_ptr->free = 0;
	mcpdm_ptr->dev = NULL;
465

466
	kfree(mcpdm_ptr);
467

468
	return 0;
469 470
}