linux/drivers/media/mmc/siano/smssdio.c
<<
>>
Prefs
   1/*
   2 *  smssdio.c - Siano 1xxx SDIO interface driver
   3 *
   4 *  Copyright 2008 Pierre Ossman
   5 *
   6 * Based on code by Siano Mobile Silicon, Inc.,
   7 * Copyright (C) 2006-2008, Uri Shkolnik
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License as published by
  11 * the Free Software Foundation; either version 2 of the License, or (at
  12 * your option) any later version.
  13 *
  14 *
  15 * This hardware is a bit odd in that all transfers should be done
  16 * to/from the SMSSDIO_DATA register, yet the "increase address" bit
  17 * always needs to be set.
  18 *
  19 * Also, buffers from the card are always aligned to 128 byte
  20 * boundaries.
  21 */
  22
  23/*
  24 * General cleanup notes:
  25 *
  26 * - only typedefs should be name *_t
  27 *
  28 * - use ERR_PTR and friends for smscore_register_device()
  29 *
  30 * - smscore_getbuffer should zero fields
  31 *
  32 * Fix stop command
  33 */
  34
  35#include <linux/moduleparam.h>
  36#include <linux/slab.h>
  37#include <linux/firmware.h>
  38#include <linux/delay.h>
  39#include <linux/mmc/card.h>
  40#include <linux/mmc/sdio_func.h>
  41#include <linux/mmc/sdio_ids.h>
  42#include <linux/module.h>
  43
  44#include "smscoreapi.h"
  45#include "sms-cards.h"
  46#include "smsendian.h"
  47
  48/* Registers */
  49
  50#define SMSSDIO_DATA            0x00
  51#define SMSSDIO_INT             0x04
  52#define SMSSDIO_BLOCK_SIZE      128
  53
  54static const struct sdio_device_id smssdio_ids[] = {
  55        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_STELLAR),
  56         .driver_data = SMS1XXX_BOARD_SIANO_STELLAR},
  57        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_NOVA_A0),
  58         .driver_data = SMS1XXX_BOARD_SIANO_NOVA_A},
  59        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_NOVA_B0),
  60         .driver_data = SMS1XXX_BOARD_SIANO_NOVA_B},
  61        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_VEGA_A0),
  62         .driver_data = SMS1XXX_BOARD_SIANO_VEGA},
  63        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, SDIO_DEVICE_ID_SIANO_VENICE),
  64         .driver_data = SMS1XXX_BOARD_SIANO_VEGA},
  65        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, 0x302),
  66        .driver_data = SMS1XXX_BOARD_SIANO_MING},
  67        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, 0x500),
  68        .driver_data = SMS1XXX_BOARD_SIANO_PELE},
  69        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, 0x600),
  70        .driver_data = SMS1XXX_BOARD_SIANO_RIO},
  71        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, 0x700),
  72        .driver_data = SMS1XXX_BOARD_SIANO_DENVER_2160},
  73        {SDIO_DEVICE(SDIO_VENDOR_ID_SIANO, 0x800),
  74        .driver_data = SMS1XXX_BOARD_SIANO_DENVER_1530},
  75        { /* end: all zeroes */ },
  76};
  77
  78MODULE_DEVICE_TABLE(sdio, smssdio_ids);
  79
  80struct smssdio_device {
  81        struct sdio_func *func;
  82
  83        struct smscore_device_t *coredev;
  84
  85        struct smscore_buffer_t *split_cb;
  86};
  87
  88/*******************************************************************/
  89/* Siano core callbacks                                            */
  90/*******************************************************************/
  91
  92static int smssdio_sendrequest(void *context, void *buffer, size_t size)
  93{
  94        int ret = 0;
  95        struct smssdio_device *smsdev;
  96
  97        smsdev = context;
  98
  99        sdio_claim_host(smsdev->func);
 100
 101        smsendian_handle_tx_message((struct sms_msg_data *) buffer);
 102        while (size >= smsdev->func->cur_blksize) {
 103                ret = sdio_memcpy_toio(smsdev->func, SMSSDIO_DATA,
 104                                        buffer, smsdev->func->cur_blksize);
 105                if (ret)
 106                        goto out;
 107
 108                buffer += smsdev->func->cur_blksize;
 109                size -= smsdev->func->cur_blksize;
 110        }
 111
 112        if (size) {
 113                ret = sdio_memcpy_toio(smsdev->func, SMSSDIO_DATA,
 114                                        buffer, size);
 115        }
 116
 117out:
 118        sdio_release_host(smsdev->func);
 119
 120        return ret;
 121}
 122
 123/*******************************************************************/
 124/* SDIO callbacks                                                  */
 125/*******************************************************************/
 126
 127static void smssdio_interrupt(struct sdio_func *func)
 128{
 129        int ret;
 130
 131        struct smssdio_device *smsdev;
 132        struct smscore_buffer_t *cb;
 133        struct sms_msg_hdr *hdr;
 134        size_t size;
 135
 136        smsdev = sdio_get_drvdata(func);
 137
 138        /*
 139         * The interrupt register has no defined meaning. It is just
 140         * a way of turning of the level triggered interrupt.
 141         */
 142        (void)sdio_readb(func, SMSSDIO_INT, &ret);
 143        if (ret) {
 144                sms_err("Unable to read interrupt register!\n");
 145                return;
 146        }
 147
 148        if (smsdev->split_cb == NULL) {
 149                cb = smscore_getbuffer(smsdev->coredev);
 150                if (!cb) {
 151                        sms_err("Unable to allocate data buffer!\n");
 152                        return;
 153                }
 154
 155                ret = sdio_memcpy_fromio(smsdev->func,
 156                                         cb->p,
 157                                         SMSSDIO_DATA,
 158                                         SMSSDIO_BLOCK_SIZE);
 159                if (ret) {
 160                        sms_err("Error %d reading initial block!\n", ret);
 161                        return;
 162                }
 163
 164                hdr = cb->p;
 165
 166                if (hdr->msg_flags & MSG_HDR_FLAG_SPLIT_MSG) {
 167                        smsdev->split_cb = cb;
 168                        return;
 169                }
 170
 171                if (hdr->msg_length > smsdev->func->cur_blksize)
 172                        size = hdr->msg_length - smsdev->func->cur_blksize;
 173                else
 174                        size = 0;
 175        } else {
 176                cb = smsdev->split_cb;
 177                hdr = cb->p;
 178
 179                size = hdr->msg_length - sizeof(struct sms_msg_hdr);
 180
 181                smsdev->split_cb = NULL;
 182        }
 183
 184        if (size) {
 185                void *buffer;
 186
 187                buffer = cb->p + (hdr->msg_length - size);
 188                size = ALIGN(size, SMSSDIO_BLOCK_SIZE);
 189
 190                BUG_ON(smsdev->func->cur_blksize != SMSSDIO_BLOCK_SIZE);
 191
 192                /*
 193                 * First attempt to transfer all of it in one go...
 194                 */
 195                ret = sdio_memcpy_fromio(smsdev->func,
 196                                         buffer,
 197                                         SMSSDIO_DATA,
 198                                         size);
 199                if (ret && ret != -EINVAL) {
 200                        smscore_putbuffer(smsdev->coredev, cb);
 201                        sms_err("Error %d reading data from card!\n", ret);
 202                        return;
 203                }
 204
 205                /*
 206                 * ..then fall back to one block at a time if that is
 207                 * not possible...
 208                 *
 209                 * (we have to do this manually because of the
 210                 * problem with the "increase address" bit)
 211                 */
 212                if (ret == -EINVAL) {
 213                        while (size) {
 214                                ret = sdio_memcpy_fromio(smsdev->func,
 215                                                  buffer, SMSSDIO_DATA,
 216                                                  smsdev->func->cur_blksize);
 217                                if (ret) {
 218                                        smscore_putbuffer(smsdev->coredev, cb);
 219                                        sms_err("Error %d reading "
 220                                                "data from card!\n", ret);
 221                                        return;
 222                                }
 223
 224                                buffer += smsdev->func->cur_blksize;
 225                                if (size > smsdev->func->cur_blksize)
 226                                        size -= smsdev->func->cur_blksize;
 227                                else
 228                                        size = 0;
 229                        }
 230                }
 231        }
 232
 233        cb->size = hdr->msg_length;
 234        cb->offset = 0;
 235
 236        smsendian_handle_rx_message((struct sms_msg_data *) cb->p);
 237        smscore_onresponse(smsdev->coredev, cb);
 238}
 239
 240static int smssdio_probe(struct sdio_func *func,
 241                         const struct sdio_device_id *id)
 242{
 243        int ret;
 244
 245        int board_id;
 246        struct smssdio_device *smsdev;
 247        struct smsdevice_params_t params;
 248
 249        board_id = id->driver_data;
 250
 251        smsdev = kzalloc(sizeof(struct smssdio_device), GFP_KERNEL);
 252        if (!smsdev)
 253                return -ENOMEM;
 254
 255        smsdev->func = func;
 256
 257        memset(&params, 0, sizeof(struct smsdevice_params_t));
 258
 259        params.device = &func->dev;
 260        params.buffer_size = 0x5000;    /* ?? */
 261        params.num_buffers = 22;        /* ?? */
 262        params.context = smsdev;
 263
 264        snprintf(params.devpath, sizeof(params.devpath),
 265                 "sdio\\%s", sdio_func_id(func));
 266
 267        params.sendrequest_handler = smssdio_sendrequest;
 268
 269        params.device_type = sms_get_board(board_id)->type;
 270
 271        if (params.device_type != SMS_STELLAR)
 272                params.flags |= SMS_DEVICE_FAMILY2;
 273        else {
 274                /*
 275                 * FIXME: Stellar needs special handling...
 276                 */
 277                ret = -ENODEV;
 278                goto free;
 279        }
 280
 281        ret = smscore_register_device(&params, &smsdev->coredev);
 282        if (ret < 0)
 283                goto free;
 284
 285        smscore_set_board_id(smsdev->coredev, board_id);
 286
 287        sdio_claim_host(func);
 288
 289        ret = sdio_enable_func(func);
 290        if (ret)
 291                goto release;
 292
 293        ret = sdio_set_block_size(func, SMSSDIO_BLOCK_SIZE);
 294        if (ret)
 295                goto disable;
 296
 297        ret = sdio_claim_irq(func, smssdio_interrupt);
 298        if (ret)
 299                goto disable;
 300
 301        sdio_set_drvdata(func, smsdev);
 302
 303        sdio_release_host(func);
 304
 305        ret = smscore_start_device(smsdev->coredev);
 306        if (ret < 0)
 307                goto reclaim;
 308
 309        return 0;
 310
 311reclaim:
 312        sdio_claim_host(func);
 313        sdio_release_irq(func);
 314disable:
 315        sdio_disable_func(func);
 316release:
 317        sdio_release_host(func);
 318        smscore_unregister_device(smsdev->coredev);
 319free:
 320        kfree(smsdev);
 321
 322        return ret;
 323}
 324
 325static void smssdio_remove(struct sdio_func *func)
 326{
 327        struct smssdio_device *smsdev;
 328
 329        smsdev = sdio_get_drvdata(func);
 330
 331        /* FIXME: racy! */
 332        if (smsdev->split_cb)
 333                smscore_putbuffer(smsdev->coredev, smsdev->split_cb);
 334
 335        smscore_unregister_device(smsdev->coredev);
 336
 337        sdio_claim_host(func);
 338        sdio_release_irq(func);
 339        sdio_disable_func(func);
 340        sdio_release_host(func);
 341
 342        kfree(smsdev);
 343}
 344
 345static struct sdio_driver smssdio_driver = {
 346        .name = "smssdio",
 347        .id_table = smssdio_ids,
 348        .probe = smssdio_probe,
 349        .remove = smssdio_remove,
 350};
 351
 352/*******************************************************************/
 353/* Module functions                                                */
 354/*******************************************************************/
 355
 356static int __init smssdio_module_init(void)
 357{
 358        int ret = 0;
 359
 360        printk(KERN_INFO "smssdio: Siano SMS1xxx SDIO driver\n");
 361        printk(KERN_INFO "smssdio: Copyright Pierre Ossman\n");
 362
 363        ret = sdio_register_driver(&smssdio_driver);
 364
 365        return ret;
 366}
 367
 368static void __exit smssdio_module_exit(void)
 369{
 370        sdio_unregister_driver(&smssdio_driver);
 371}
 372
 373module_init(smssdio_module_init);
 374module_exit(smssdio_module_exit);
 375
 376MODULE_DESCRIPTION("Siano SMS1xxx SDIO driver");
 377MODULE_AUTHOR("Pierre Ossman");
 378MODULE_LICENSE("GPL");
 379