aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/omap24xx/base-files/etc
Commit message (Expand)AuthorAgeFilesLines
* omap24xx: Remove unmaintained targetHauke Mehrtens2019-05-038-120/+0
* merge: ssid: update default ssidZoltan HERPAI2017-12-081-1/+1
* treewide: use only board_name function to get nameMathias Kresin2017-07-152-2/+6
* image / basefiles: make console password configurableJohn Crispin2016-04-181-3/+3
* branding: add LEDE brandingJohn Crispin2016-03-241-1/+1
* n810: Add usb networking to default net configMichael Büsch2011-03-071-1/+7
* omap24xx: Set hardware watchdog timeout to maxMichael Büsch2011-03-061-1/+1
* omap24xx: Start watchdog with RT priorityMichael Büsch2011-03-061-5/+7
* omap24xx: Use noatime for maemo partitionsMichael Büsch2011-02-201-2/+2
* n810: Add firmware generator hotplug scriptsMichael Büsch2011-02-062-0/+45
* omap24xx: Move p54 eeprom init to preinitMichael Büsch2011-01-291-26/+0
* Add p54spi EEPROM initscriptMichael Büsch2011-01-291-0/+26
* omap24xx: Don't automatically mount maemo fsMichael Büsch2011-01-271-2/+2
* omap24xx: Fix serial console for .37Michael Büsch2011-01-271-1/+1
* base-files: use shutdown instead of stop when the system goes down (patch by ...Felix Fietkau2010-12-131-1/+1
* n810: Add fstab for maemo filesystemsMichael Büsch2010-10-031-0/+13
* omap24xx: Add default network configMichael Büsch2010-09-112-0/+29
* omap24xx: Add pointercal fileMichael Büsch2010-09-111-0/+1
* omap24xx: Add working inittab and watchdog init scriptMichael Büsch2010-08-082-0/+20
href='#n216'>216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 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 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 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 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 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -442,6 +442,8 @@ struct spi_transfer {
 	u16		delay_usecs;
 	u32		speed_hz;
 
+	unsigned	last_in_message_list;
+
 	struct list_head transfer_list;
 };
 
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -152,6 +152,14 @@ config SPI_GPIO_OLD
 
 	  If unsure, say N.
 
+config SPI_CNS21XX
+	tristate "Cavium Netowrks CNS21xx SPI master"
+	depends on ARCH_CNS21XX && EXPERIMENTAL
+	select SPI_BITBANG
+	help
+	  This driver supports the buil-in SPI controller of the Cavium Networks
+	  CNS21xx SoCs.
+
 config SPI_IMX
 	tristate "Freescale i.MX SPI controllers"
 	depends on ARCH_MXC
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.
 obj-$(CONFIG_SPI_SH_MSIOF)		+= spi_sh_msiof.o
 obj-$(CONFIG_SPI_STMP3XXX)		+= spi_stmp.o
 obj-$(CONFIG_SPI_NUC900)		+= spi_nuc900.o
+obj-$(CONFIG_SPI_CNS21XX)		+= spi_cns21xx.o
 
 # special build for s3c24xx spi driver with fiq support
 spi_s3c24xx_hw-y			:= spi_s3c24xx.o
--- a/drivers/spi/spi_bitbang.c
+++ b/drivers/spi/spi_bitbang.c
@@ -337,6 +337,13 @@ static void bitbang_work(struct work_str
 				 */
 				if (!m->is_dma_mapped)
 					t->rx_dma = t->tx_dma = 0;
+
+				if (t->transfer_list.next == &m->transfers) {
+					t->last_in_message_list = 1;
+				} else {
+					t->last_in_message_list = 0;
+				}
+
 				status = bitbang->txrx_bufs(spi, t);
 			}
 			if (status > 0)
--- /dev/null
+++ b/drivers/spi/spi_cns21xx.c
@@ -0,0 +1,520 @@
+/*
+ *  Copyright (c) 2008 Cavium Networks
+ *  Copyright (c) 2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  This file 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.
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+
+#include <mach/hardware.h>
+#include <mach/cns21xx.h>
+
+#define DRIVER_NAME	"cns21xx-spi"
+
+#ifdef CONFIG_CNS21XX_SPI_DEBUG
+#define DBG(fmt, args...)	pr_info("[CNS21XX_SPI_DEBUG]" fmt, ## args)
+#else
+#define DBG(fmt, args...)	do {} while (0)
+#endif /*  CNS21XX_SPI_DEBUG */
+
+#define SPI_REG_CFG			0x40
+#define SPI_REG_STAT			0x44
+#define SPI_REG_BIT_RATE		0x48
+#define SPI_REG_TX_CTRL			0x4c
+#define SPI_REG_TX_DATA			0x50
+#define SPI_REG_RX_CTRL			0x54
+#define SPI_REG_RX_DATA			0x58
+#define SPI_REG_FIFO_TX_CFG		0x5c
+#define SPI_REG_FIFO_TX_CTRL		0x60
+#define SPI_REG_FIFO_RX_CFG		0x64
+#define SPI_REG_INTR_STAT		0x68
+#define SPI_REG_INTR_ENA		0x6c
+
+#define CFG_SPI_EN			BIT(31)
+#define CFG_SPI_CLKPOL			BIT(14)
+#define CFG_SPI_CLKPHA			BIT(13)
+#define CFG_SPI_MASTER_EN		BIT(11)
+#define CFG_SPI_CHAR_LEN_M		0x3
+#define CFG_SPI_CHAR_LEN_8BITS		0
+#define CFG_SPI_CHAR_LEN_16BITS		1
+#define CFG_SPI_CHAR_LEN_24BITS		2
+#define CFG_SPI_CHAR_LEN_32BITS		3
+
+#define STAT_SPI_BUSY_STA		BIT(1)
+
+#define BIT_RATE_DIV_1			0
+#define BIT_RATE_DIV_2			1
+#define BIT_RATE_DIV_4			2
+#define BIT_RATE_DIV_8			3
+#define BIT_RATE_DIV_16			4
+#define BIT_RATE_DIV_32			5
+#define BIT_RATE_DIV_64			6
+#define BIT_RATE_DIV_128		7
+
+#define TX_CTRL_SPI_TXDAT_EOF		BIT(2)
+#define TX_CTRL_SPI_TXCH_NUM_M		0x3
+#define TX_CTRL_CLEAR_MASK		(TX_CTRL_SPI_TXDAT_EOF | \
+					 TX_CTRL_SPI_TXCH_NUM_M)
+
+#define RX_CTRL_SPI_RXDAT_EOF		BIT(2)
+#define RX_CTRL_SPI_RXCH_NUM_M		0x3
+
+#define INTR_STAT_SPI_TXBF_UNRN_FG	BIT(7)
+#define INTR_STAT_SPI_RXBF_OVRN_FG	BIT(6)
+#define INTR_STAT_SPI_TXFF_UNRN_FG	BIT(5)
+#define INTR_STAT_SPI_RXFF_OVRN_FG	BIT(4)
+#define INTR_STAT_SPI_TXBUF_FG		BIT(3)
+#define INTR_STAT_SPI_RXBUF_FG		BIT(2)
+#define INTR_STAT_SPI_TXFF_FG		BIT(1)
+#define INTR_STAT_SPI_RXFF_FG		BIT(0)
+
+#define INTR_STAT_CLEAR_MASK		(INTR_STAT_SPI_TXBF_UNRN_FG | \
+					 INTR_STAT_SPI_RXBF_OVRN_FG | \
+					 INTR_STAT_SPI_TXFF_UNRN_FG | \
+					 INTR_STAT_SPI_RXFF_OVRN_FG)
+
+#define FIFO_TX_CFG_SPI_TXFF_THRED_M	0x3
+#define FIFO_TX_CFG_SPI_TXFF_THRED_S	4
+#define FIFO_TX_CFG_SPI_TXFF_THRED_2	0
+#define FIFO_TX_CFG_SPI_TXFF_THRED_4	1
+#define FIFO_TX_CFG_SPI_TXFF_THRED_6	0
+#define FIFO_TX_CFG_SPI_TXFF_STATUS_M	0xf
+
+#define FIFO_RX_CFG_SPI_RXFF_THRED_M	0x3
+#define FIFO_RX_CFG_SPI_RXFF_THRED_S	4
+#define FIFO_RX_CFG_SPI_RXFF_THRED_2	0
+#define FIFO_RX_CFG_SPI_RXFF_THRED_4	1
+#define FIFO_RX_CFG_SPI_RXFF_THRED_6	0
+#define FIFO_RX_CFG_SPI_RXFF_STATUS_M	0xf
+
+#define CNS21XX_SPI_NUM_BIT_RATES	8
+
+struct cns21xx_spi {
+	struct spi_bitbang	bitbang;
+
+	struct spi_master	*master;
+	struct device		*dev;
+	void __iomem		*base;
+	struct resource		*region;
+
+	unsigned		freq_max;
+	unsigned		freq_min;
+
+};
+
+static inline struct cns21xx_spi *to_hw(struct spi_device *spi)
+{
+	return spi_master_get_devdata(spi->master);
+}
+
+static inline u32 cns21xx_spi_rr(struct cns21xx_spi *hw, unsigned int reg)
+{
+	return __raw_readl(hw->base + reg);
+}
+
+static inline void cns21xx_spi_wr(struct cns21xx_spi *hw, u32 val,
+				  unsigned int reg)
+{
+	__raw_writel(val, hw->base + reg);
+}
+
+#define CNS21XX_SPI_RETRY_COUNT		100
+static inline int cns21xx_spi_wait(struct cns21xx_spi *hw, unsigned int reg,
+				   u32 mask, u32 val)
+{
+	int retry_cnt = 0;
+
+	do {
+		if ((cns21xx_spi_rr(hw, reg) & mask) == val)
+			break;
+
+		if (++retry_cnt > CNS21XX_SPI_RETRY_COUNT) {
+			dev_err(hw->dev, "timeout waiting on register %02x\n",
+				reg);
+			return -EIO;
+		}
+	} while (1);
+
+	return 0;
+}
+
+static int cns21xx_spi_txrx_word(struct cns21xx_spi *hw, u8 tx_channel,
+				 u8 tx_eof_flag, u32 tx_data, u32 *rx_data)
+{
+	unsigned int tx_ctrl;
+	u8 rx_channel;
+	u8 rx_eof_flag;
+	int err = 0;
+
+	err = cns21xx_spi_wait(hw, SPI_REG_STAT, STAT_SPI_BUSY_STA, 0);
+	if (err)
+		return err;
+
+	err = cns21xx_spi_wait(hw, SPI_REG_INTR_STAT, INTR_STAT_SPI_TXBUF_FG,
+			       INTR_STAT_SPI_TXBUF_FG);
+	if (err)
+		return err;
+
+	tx_ctrl = cns21xx_spi_rr(hw, SPI_REG_TX_CTRL);
+	tx_ctrl &= ~(TX_CTRL_CLEAR_MASK);
+	tx_ctrl |= (tx_channel & TX_CTRL_SPI_TXCH_NUM_M);
+	tx_ctrl |= (tx_eof_flag) ? TX_CTRL_SPI_TXDAT_EOF : 0;
+	cns21xx_spi_wr(hw, tx_ctrl, SPI_REG_TX_CTRL);
+
+	cns21xx_spi_wr(hw, tx_data, SPI_REG_TX_DATA);
+
+	err = cns21xx_spi_wait(hw, SPI_REG_INTR_STAT, INTR_STAT_SPI_RXBUF_FG,
+			       INTR_STAT_SPI_RXBUF_FG);
+	if (err)
+		return err;
+
+	rx_channel = cns21xx_spi_rr(hw, SPI_REG_RX_CTRL) &
+					RX_CTRL_SPI_RXCH_NUM_M;
+
+	rx_eof_flag = (cns21xx_spi_rr(hw, SPI_REG_RX_CTRL) &
+					RX_CTRL_SPI_RXDAT_EOF) ? 1 : 0;
+
+	*rx_data = cns21xx_spi_rr(hw, SPI_REG_RX_DATA);
+
+	if ((tx_channel != rx_channel) || (tx_eof_flag != rx_eof_flag))
+		return -EPROTO;
+
+	return 0;
+}
+
+static void cns21xx_spi_chipselect(struct spi_device *spi, int value)
+{
+	struct cns21xx_spi *hw = to_hw(spi);
+	unsigned int spi_config;
+	unsigned int tx_ctrl;
+
+	switch (value) {
+	case BITBANG_CS_INACTIVE:
+		break;
+
+	case BITBANG_CS_ACTIVE:
+		spi_config = cns21xx_spi_rr(hw, SPI_REG_CFG);
+
+		if (spi->mode & SPI_CPHA)
+			spi_config |= CFG_SPI_CLKPHA;
+		else
+			spi_config &= ~CFG_SPI_CLKPHA;
+
+		if (spi->mode & SPI_CPOL)
+			spi_config |= CFG_SPI_CLKPOL;
+		else
+			spi_config &= ~CFG_SPI_CLKPOL;
+
+		cns21xx_spi_wr(hw, spi_config, SPI_REG_CFG);
+
+		tx_ctrl = cns21xx_spi_rr(hw, SPI_REG_TX_CTRL);
+		tx_ctrl &= ~(TX_CTRL_CLEAR_MASK);
+		tx_ctrl |= (spi->chip_select & TX_CTRL_SPI_TXCH_NUM_M);
+		cns21xx_spi_wr(hw, tx_ctrl, SPI_REG_TX_CTRL);
+
+		break;
+	}
+}
+
+static int cns21xx_spi_setup(struct spi_device *spi)
+{
+	struct cns21xx_spi *hw = to_hw(spi);
+
+	if (spi->bits_per_word != 8) {
+		dev_err(&spi->dev, "%s: invalid bits_per_word=%u\n",
+			__func__, spi->bits_per_word);
+		return -EINVAL;
+	}
+
+	if (spi->max_speed_hz == 0)
+		spi->max_speed_hz = hw->freq_max;
+
+	if (spi->max_speed_hz > hw->freq_max ||
+	    spi->max_speed_hz < hw->freq_min) {
+		dev_err(&spi->dev, "%s: max_speed_hz=%u out of range\n",
+			__func__, spi->max_speed_hz);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cns21xx_spi_setup_transfer(struct spi_device *spi,
+				      struct spi_transfer *t)
+{
+	struct cns21xx_spi *hw = to_hw(spi);
+	u8	bits_per_word;
+	u32	hz;
+	int	i;
+
+	bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word;
+	hz = t ? t->speed_hz : spi->max_speed_hz;
+
+	if (!bits_per_word)
+		bits_per_word = spi->bits_per_word;
+
+	if (!hz)
+		hz = spi->max_speed_hz;
+
+	if (bits_per_word != 8) {
+		dev_err(&spi->dev, "%s: invalid bits_per_word=%u\n",
+			__func__, bits_per_word);
+		return -EINVAL;
+	}
+
+	if (hz > spi->max_speed_hz || hz > hw->freq_max || hz < hw->freq_min) {
+		dev_err(&spi->dev, "%s: max_speed_hz=%u out of range\n",
+			__func__, hz);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < CNS21XX_SPI_NUM_BIT_RATES; i++)
+		if (spi->max_speed_hz > (cns21xx_get_apb_freq() >> i))
+			break;
+
+	DBG("max_speed:%uHz, curr_speed:%luHz, rate_index=%d\n",
+	    spi->max_speed_hz, cns21xx_get_apb_freq() / (1 << i), i);
+
+	cns21xx_spi_wr(hw, i, SPI_REG_BIT_RATE);
+
+	return 0;
+}
+
+static int cns21xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct cns21xx_spi *hw = to_hw(spi);
+	const unsigned char *tx_buf;
+	unsigned char *rx_buf;
+	u32 rx_data;
+	int tx_eof;
+	int err = 0;
+	int i;
+
+	tx_buf = t->tx_buf;
+	rx_buf = t->rx_buf;
+	tx_eof = t->last_in_message_list;
+
+	DBG("txrx: tx %p, rx %p, len %d\n", tx_buf, rx_buf, t->len);
+
+	if (tx_buf) {
+		for (i = 0; i < t->len; i++)
+			DBG("tx_buf[%02d]: 0x%02x\n", i, tx_buf[i]);
+
+		for (i = 0; i < (t->len - 1); i++) {
+			err = cns21xx_spi_txrx_word(hw, spi->chip_select, 0,
+						    tx_buf[i], &rx_data);
+			if (err)
+				goto done;
+
+			if (rx_buf) {
+				rx_buf[i] = rx_data;
+				DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]);
+			}
+		}
+
+		err = cns21xx_spi_txrx_word(hw, spi->chip_select, tx_eof,
+					    tx_buf[i], &rx_data);
+		if (err)
+			goto done;
+
+		if ((tx_eof) && rx_buf) {
+			rx_buf[i] = rx_data;
+			DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]);
+		}
+	} else if (rx_buf) {
+		for (i = 0; i < (t->len - 1); i++) {
+			err = cns21xx_spi_txrx_word(hw, spi->chip_select, 0,
+						    0xff, &rx_data);
+			if (err)
+				goto done;
+
+			rx_buf[i] = rx_data;
+			DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]);
+		}
+
+		err = cns21xx_spi_txrx_word(hw, spi->chip_select, tx_eof,
+					    0xff, &rx_data);
+		if (err)
+			goto done;
+
+		rx_buf[i] = rx_data;
+		DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]);
+	}
+
+ done:
+	return (err) ? err : t->len;
+}
+
+static void __init cns21xx_spi_hw_init(struct cns21xx_spi *hw)
+{
+	u32 t;
+	u32 pclk;
+
+	/* Setup configuration register */
+	cns21xx_spi_wr(hw, CFG_SPI_MASTER_EN, SPI_REG_CFG);
+
+	/* Set default clock to PCLK/2 */
+	cns21xx_spi_wr(hw, BIT_RATE_DIV_2, SPI_REG_BIT_RATE);
+
+	/* Configure SPI's Tx channel */
+	cns21xx_spi_wr(hw, 0, SPI_REG_TX_CTRL);
+
+	/* Configure Tx FIFO Threshold */
+	t = cns21xx_spi_rr(hw, SPI_REG_FIFO_TX_CFG);
+	t &= ~(FIFO_TX_CFG_SPI_TXFF_THRED_M << FIFO_TX_CFG_SPI_TXFF_THRED_S);
+	t |= (FIFO_TX_CFG_SPI_TXFF_THRED_2 << FIFO_TX_CFG_SPI_TXFF_THRED_S);
+	cns21xx_spi_wr(hw, t, SPI_REG_FIFO_TX_CFG);
+
+	/* Configure Rx FIFO Threshold */
+	t = cns21xx_spi_rr(hw, SPI_REG_FIFO_RX_CFG);
+	t &= ~(FIFO_RX_CFG_SPI_RXFF_THRED_M << FIFO_RX_CFG_SPI_RXFF_THRED_S);
+	t |= (FIFO_RX_CFG_SPI_RXFF_THRED_2 << FIFO_RX_CFG_SPI_RXFF_THRED_S);
+	cns21xx_spi_wr(hw, t, SPI_REG_FIFO_RX_CFG);
+
+	/* Disable interrupts, and clear interrupt status */
+	cns21xx_spi_wr(hw, 0, SPI_REG_INTR_ENA);
+	cns21xx_spi_wr(hw, INTR_STAT_CLEAR_MASK, SPI_REG_INTR_STAT);
+
+	(void) cns21xx_spi_rr(hw, SPI_REG_RX_DATA);
+
+	/* Enable SPI */
+	t = cns21xx_spi_rr(hw, SPI_REG_CFG);
+	t |= CFG_SPI_EN;
+	cns21xx_spi_wr(hw, t, SPI_REG_CFG);
+
+	pclk = cns21xx_get_apb_freq();
+	hw->freq_max = pclk;
+	hw->freq_min = pclk / (1 << BIT_RATE_DIV_128);
+}
+
+static int __init cns21xx_spi_probe(struct platform_device *pdev)
+{
+	struct cns21xx_spi *hw;
+	struct spi_master *master;
+	struct resource *res;
+	int err = 0;
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct cns21xx_spi));
+	if (!master) {
+		dev_err(&pdev->dev, "No memory for spi_master\n");
+		return -ENOMEM;
+	}
+
+	hw = spi_master_get_devdata(master);
+
+	platform_set_drvdata(pdev, hw);
+	hw->master = spi_master_get(master);
+	hw->dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_dbg(&pdev->dev, "no MEM resource found\n");
+		err = -ENOENT;
+		goto err_put_master;
+	}
+
+	hw->region = request_mem_region(res->start, resource_size(res),
+					dev_name(&pdev->dev));
+	if (!hw->region) {
+		dev_err(&pdev->dev, "unable to reserve iomem region\n");
+		err = -ENXIO;
+		goto err_put_master;
+	}
+
+	hw->base = ioremap(res->start, resource_size(res));
+	if (!hw->base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		err = -ENOENT;
+		goto err_release_region;
+	}
+
+	cns21xx_spi_hw_init(hw);
+
+	master->bus_num = pdev->id;
+	if (master->bus_num == -1)
+		master->bus_num = 0;
+
+	master->num_chipselect = 4;
+	master->setup = cns21xx_spi_setup;
+
+	hw->bitbang.master = hw->master;
+	hw->bitbang.chipselect = cns21xx_spi_chipselect;
+	hw->bitbang.txrx_bufs = cns21xx_spi_txrx;
+	hw->bitbang.setup_transfer = cns21xx_spi_setup_transfer;
+
+	err = spi_bitbang_start(&hw->bitbang);
+	if (err) {
+		dev_err(hw->dev, "unable to register SPI master\n");
+		goto err_unmap;
+	}
+
+	dev_info(hw->dev, "iomem at %08x\n", res->start);
+
+	return 0;
+
+ err_unmap:
+	iounmap(hw->base);
+
+ err_release_region:
+	release_resource(hw->region);
+	kfree(hw->region);
+
+ err_put_master:
+	spi_master_put(hw->bitbang.master);
+	platform_set_drvdata(pdev, NULL);
+
+	return err;
+}
+
+static int __devexit cns21xx_spi_remove(struct platform_device *pdev)
+{
+	struct cns21xx_spi *hw = platform_get_drvdata(pdev);
+
+	spi_bitbang_stop(&hw->bitbang);
+	iounmap(hw->base);
+	release_resource(hw->region);
+	kfree(hw->region);
+	spi_master_put(hw->bitbang.master);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver cns21xx_spi_driver = {
+	.remove		= __devexit_p(cns21xx_spi_remove),
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init cns21xx_spi_init(void)
+{
+	return platform_driver_probe(&cns21xx_spi_driver, cns21xx_spi_probe);
+}
+
+static void __exit cns21xx_spi_exit(void)
+{
+	platform_driver_unregister(&cns21xx_spi_driver);
+}
+
+module_init(cns21xx_spi_init);
+module_exit(cns21xx_spi_exit);
+
+MODULE_DESCRIPTION("Cavium Networks CNS21xx SPI Controller driver");
+MODULE_AUTHOR("STAR Semi Corp.");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);