| // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) |
| /* |
| * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles |
| * |
| * Main part |
| * |
| * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria |
| * |
| * If distributed as part of the Linux kernel, this code is licensed under the |
| * terms of the GPL v2. |
| * |
| * Otherwise, the following license terms apply: |
| * |
| * * Redistribution and use in source and binary forms, with or without |
| * * modification, are permitted provided that the following conditions |
| * * are met: |
| * * 1) Redistributions of source code must retain the above copyright |
| * * notice, this list of conditions and the following disclaimer. |
| * * 2) Redistributions in binary form must reproduce the above copyright |
| * * notice, this list of conditions and the following disclaimer in the |
| * * documentation and/or other materials provided with the distribution. |
| * * 3) The name of the author may not be used to endorse or promote products |
| * * derived from this software without specific psisusbr written permission. |
| * * |
| * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR |
| * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Author: Thomas Winischhofer <thomas@winischhofer.net> |
| * |
| */ |
| |
| #include <linux/mutex.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/signal.h> |
| #include <linux/errno.h> |
| #include <linux/poll.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/kref.h> |
| #include <linux/usb.h> |
| #include <linux/vmalloc.h> |
| |
| #include "sisusb.h" |
| |
| #define SISUSB_DONTSYNC |
| |
| /* Forward declarations / clean-up routines */ |
| |
| static struct usb_driver sisusb_driver; |
| |
| static void sisusb_free_buffers(struct sisusb_usb_data *sisusb) |
| { |
| int i; |
| |
| for (i = 0; i < NUMOBUFS; i++) { |
| kfree(sisusb->obuf[i]); |
| sisusb->obuf[i] = NULL; |
| } |
| kfree(sisusb->ibuf); |
| sisusb->ibuf = NULL; |
| } |
| |
| static void sisusb_free_urbs(struct sisusb_usb_data *sisusb) |
| { |
| int i; |
| |
| for (i = 0; i < NUMOBUFS; i++) { |
| usb_free_urb(sisusb->sisurbout[i]); |
| sisusb->sisurbout[i] = NULL; |
| } |
| usb_free_urb(sisusb->sisurbin); |
| sisusb->sisurbin = NULL; |
| } |
| |
| /* Level 0: USB transport layer */ |
| |
| /* 1. out-bulks */ |
| |
| /* out-urb management */ |
| |
| /* Return 1 if all free, 0 otherwise */ |
| static int sisusb_all_free(struct sisusb_usb_data *sisusb) |
| { |
| int i; |
| |
| for (i = 0; i < sisusb->numobufs; i++) { |
| |
| if (sisusb->urbstatus[i] & SU_URB_BUSY) |
| return 0; |
| |
| } |
| |
| return 1; |
| } |
| |
| /* Kill all busy URBs */ |
| static void sisusb_kill_all_busy(struct sisusb_usb_data *sisusb) |
| { |
| int i; |
| |
| if (sisusb_all_free(sisusb)) |
| return; |
| |
| for (i = 0; i < sisusb->numobufs; i++) { |
| |
| if (sisusb->urbstatus[i] & SU_URB_BUSY) |
| usb_kill_urb(sisusb->sisurbout[i]); |
| |
| } |
| } |
| |
| /* Return 1 if ok, 0 if error (not all complete within timeout) */ |
| static int sisusb_wait_all_out_complete(struct sisusb_usb_data *sisusb) |
| { |
| int timeout = 5 * HZ, i = 1; |
| |
| wait_event_timeout(sisusb->wait_q, (i = sisusb_all_free(sisusb)), |
| timeout); |
| |
| return i; |
| } |
| |
| static int sisusb_outurb_available(struct sisusb_usb_data *sisusb) |
| { |
| int i; |
| |
| for (i = 0; i < sisusb->numobufs; i++) { |
| |
| if ((sisusb->urbstatus[i] & (SU_URB_BUSY|SU_URB_ALLOC)) == 0) |
| return i; |
| |
| } |
| |
| return -1; |
| } |
| |
| static int sisusb_get_free_outbuf(struct sisusb_usb_data *sisusb) |
| { |
| int i, timeout = 5 * HZ; |
| |
| wait_event_timeout(sisusb->wait_q, |
| ((i = sisusb_outurb_available(sisusb)) >= 0), timeout); |
| |
| return i; |
| } |
| |
| static int sisusb_alloc_outbuf(struct sisusb_usb_data *sisusb) |
| { |
| int i; |
| |
| i = sisusb_outurb_available(sisusb); |
| |
| if (i >= 0) |
| sisusb->urbstatus[i] |= SU_URB_ALLOC; |
| |
| return i; |
| } |
| |
| static void sisusb_free_outbuf(struct sisusb_usb_data *sisusb, int index) |
| { |
| if ((index >= 0) && (index < sisusb->numobufs)) |
| sisusb->urbstatus[index] &= ~SU_URB_ALLOC; |
| } |
| |
| /* completion callback */ |
| |
| static void sisusb_bulk_completeout(struct urb *urb) |
| { |
| struct sisusb_urb_context *context = urb->context; |
| struct sisusb_usb_data *sisusb; |
| |
| if (!context) |
| return; |
| |
| sisusb = context->sisusb; |
| |
| if (!sisusb || !sisusb->sisusb_dev || !sisusb->present) |
| return; |
| |
| #ifndef SISUSB_DONTSYNC |
| if (context->actual_length) |
| *(context->actual_length) += urb->actual_length; |
| #endif |
| |
| sisusb->urbstatus[context->urbindex] &= ~SU_URB_BUSY; |
| wake_up(&sisusb->wait_q); |
| } |
| |
| static int sisusb_bulkout_msg(struct sisusb_usb_data *sisusb, int index, |
| unsigned int pipe, void *data, int len, int *actual_length, |
| int timeout, unsigned int tflags) |
| { |
| struct urb *urb = sisusb->sisurbout[index]; |
| int retval, byteswritten = 0; |
| |
| /* Set up URB */ |
| urb->transfer_flags = 0; |
| |
| usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len, |
| sisusb_bulk_completeout, |
| &sisusb->urbout_context[index]); |
| |
| urb->transfer_flags |= tflags; |
| urb->actual_length = 0; |
| |
| /* Set up context */ |
| sisusb->urbout_context[index].actual_length = (timeout) ? |
| NULL : actual_length; |
| |
| /* Declare this urb/buffer in use */ |
| sisusb->urbstatus[index] |= SU_URB_BUSY; |
| |
| /* Submit URB */ |
| retval = usb_submit_urb(urb, GFP_KERNEL); |
| |
| /* If OK, and if timeout > 0, wait for completion */ |
| if ((retval == 0) && timeout) { |
| wait_event_timeout(sisusb->wait_q, |
| (!(sisusb->urbstatus[index] & SU_URB_BUSY)), |
| timeout); |
| if (sisusb->urbstatus[index] & SU_URB_BUSY) { |
| /* URB timed out... kill it and report error */ |
| usb_kill_urb(urb); |
| retval = -ETIMEDOUT; |
| } else { |
| /* Otherwise, report urb status */ |
| retval = urb->status; |
| byteswritten = urb->actual_length; |
| } |
| } |
| |
| if (actual_length) |
| *actual_length = byteswritten; |
| |
| return retval; |
| } |
| |
| /* 2. in-bulks */ |
| |
| /* completion callback */ |
| |
| static void sisusb_bulk_completein(struct urb *urb) |
| { |
| struct sisusb_usb_data *sisusb = urb->context; |
| |
| if (!sisusb || !sisusb->sisusb_dev || !sisusb->present) |
| return; |
| |
| sisusb->completein = 1; |
| wake_up(&sisusb->wait_q); |
| } |
| |
| static int sisusb_bulkin_msg(struct sisusb_usb_data *sisusb, |
| unsigned int pipe, void *data, int len, |
| int *actual_length, int timeout, unsigned int tflags) |
| { |
| struct urb *urb = sisusb->sisurbin; |
| int retval, readbytes = 0; |
| |
| urb->transfer_flags = 0; |
| |
| usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len, |
| sisusb_bulk_completein, sisusb); |
| |
| urb->transfer_flags |= tflags; |
| urb->actual_length = 0; |
| |
| sisusb->completein = 0; |
| retval = usb_submit_urb(urb, GFP_KERNEL); |
| if (retval == 0) { |
| wait_event_timeout(sisusb->wait_q, sisusb->completein, timeout); |
| if (!sisusb->completein) { |
| /* URB timed out... kill it and report error */ |
| usb_kill_urb(urb); |
| retval = -ETIMEDOUT; |
| } else { |
| /* URB completed within timeout */ |
| retval = urb->status; |
| readbytes = urb->actual_length; |
| } |
| } |
| |
| if (actual_length) |
| *actual_length = readbytes; |
| |
| return retval; |
| } |
| |
| |
| /* Level 1: */ |
| |
| /* Send a bulk message of variable size |
| * |
| * To copy the data from userspace, give pointer to "userbuffer", |
| * to copy from (non-DMA) kernel memory, give "kernbuffer". If |
| * both of these are NULL, it is assumed, that the transfer |
| * buffer "sisusb->obuf[index]" is set up with the data to send. |
| * Index is ignored if either kernbuffer or userbuffer is set. |
| * If async is nonzero, URBs will be sent without waiting for |
| * completion of the previous URB. |
| * |
| * (return 0 on success) |
| */ |
| |
| static int sisusb_send_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len, |
| char *kernbuffer, const char __user *userbuffer, int index, |
| ssize_t *bytes_written, unsigned int tflags, int async) |
| { |
| int result = 0, retry, count = len; |
| int passsize, thispass, transferred_len = 0; |
| int fromuser = (userbuffer != NULL) ? 1 : 0; |
| int fromkern = (kernbuffer != NULL) ? 1 : 0; |
| unsigned int pipe; |
| char *buffer; |
| |
| (*bytes_written) = 0; |
| |
| /* Sanity check */ |
| if (!sisusb || !sisusb->present || !sisusb->sisusb_dev) |
| return -ENODEV; |
| |
| /* If we copy data from kernel or userspace, force the |
| * allocation of a buffer/urb. If we have the data in |
| * the transfer buffer[index] already, reuse the buffer/URB |
| * if the length is > buffer size. (So, transmitting |
| * large data amounts directly from the transfer buffer |
| * treats the buffer as a ring buffer. However, we need |
| * to sync in this case.) |
| */ |
| if (fromuser || fromkern) |
| index = -1; |
| else if (len > sisusb->obufsize) |
| async = 0; |
| |
| pipe = usb_sndbulkpipe(sisusb->sisusb_dev, ep); |
| |
| do { |
| passsize = thispass = (sisusb->obufsize < count) ? |
| sisusb->obufsize : count; |
| |
| if (index < 0) |
| index = sisusb_get_free_outbuf(sisusb); |
| |
| if (index < 0) |
| return -EIO; |
| |
| buffer = sisusb->obuf[index]; |
| |
| if (fromuser) { |
| |
| if (copy_from_user(buffer, userbuffer, passsize)) |
| return -EFAULT; |
| |
| userbuffer += passsize; |
| |
| } else if (fromkern) { |
| |
| memcpy(buffer, kernbuffer, passsize); |
| kernbuffer += passsize; |
| |
| } |
| |
| retry = 5; |
| while (thispass) { |
| |
| if (!sisusb->sisusb_dev) |
| return -ENODEV; |
| |
| result = sisusb_bulkout_msg(sisusb, index, pipe, |
| buffer, thispass, &transferred_len, |
| async ? 0 : 5 * HZ, tflags); |
| |
| if (result == -ETIMEDOUT) { |
| |
| /* Will not happen if async */ |
| if (!retry--) |
| return -ETIME; |
| |
| continue; |
| } |
| |
| if ((result == 0) && !async && transferred_len) { |
| |
| thispass -= transferred_len; |
| buffer += transferred_len; |
| |
| } else |
| break; |
| } |
| |
| if (result) |
| return result; |
| |
| (*bytes_written) += passsize; |
| count -= passsize; |
| |
| /* Force new allocation in next iteration */ |
| if (fromuser || fromkern) |
| index = -1; |
| |
| } while (count > 0); |
| |
| if (async) { |
| #ifdef SISUSB_DONTSYNC |
| (*bytes_written) = len; |
| /* Some URBs/buffers might be busy */ |
| #else |
| sisusb_wait_all_out_complete(sisusb); |
| (*bytes_written) = transferred_len; |
| /* All URBs and all buffers are available */ |
| #endif |
| } |
| |
| return ((*bytes_written) == len) ? 0 : -EIO; |
| } |
| |
| /* Receive a bulk message of variable size |
| * |
| * To copy the data to userspace, give pointer to "userbuffer", |
| * to copy to kernel memory, give "kernbuffer". One of them |
| * MUST be set. (There is no technique for letting the caller |
| * read directly from the ibuf.) |
| * |
| */ |
| |
| static int sisusb_recv_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len, |
| void *kernbuffer, char __user *userbuffer, ssize_t *bytes_read, |
| unsigned int tflags) |
| { |
| int result = 0, retry, count = len; |
| int bufsize, thispass, transferred_len; |
| unsigned int pipe; |
| char *buffer; |
| |
| (*bytes_read) = 0; |
| |
| /* Sanity check */ |
| if (!sisusb || !sisusb->present || !sisusb->sisusb_dev) |
| return -ENODEV; |
| |
| pipe = usb_rcvbulkpipe(sisusb->sisusb_dev, ep); |
| buffer = sisusb->ibuf; |
| bufsize = sisusb->ibufsize; |
| |
| retry = 5; |
| |
| #ifdef SISUSB_DONTSYNC |
| if (!(sisusb_wait_all_out_complete(sisusb))) |
| return -EIO; |
| #endif |
| |
| while (count > 0) { |
| |
| if (!sisusb->sisusb_dev) |
| return -ENODEV; |
| |
| thispass = (bufsize < count) ? bufsize : count; |
| |
| result = sisusb_bulkin_msg(sisusb, pipe, buffer, thispass, |
| &transferred_len, 5 * HZ, tflags); |
| |
| if (transferred_len) |
| thispass = transferred_len; |
| |
| else if (result == -ETIMEDOUT) { |
| |
| if (!retry--) |
| return -ETIME; |
| |
| continue; |
| |
| } else |
| return -EIO; |
| |
| |
| if (thispass) { |
| |
| (*bytes_read) += thispass; |
| count -= thispass; |
| |
| if (userbuffer) { |
| |
| if (copy_to_user(userbuffer, buffer, thispass)) |
| return -EFAULT; |
| |
| userbuffer += thispass; |
| |
| } else { |
| |
| memcpy(kernbuffer, buffer, thispass); |
| kernbuffer += thispass; |
| |
| } |
| |
| } |
| |
| } |
| |
| return ((*bytes_read) == len) ? 0 : -EIO; |
| } |
| |
| static int sisusb_send_packet(struct sisusb_usb_data *sisusb, int len, |
| struct sisusb_packet *packet) |
| { |
| int ret; |
| ssize_t bytes_transferred = 0; |
| __le32 tmp; |
| |
| if (len == 6) |
| packet->data = 0; |
| |
| #ifdef SISUSB_DONTSYNC |
| if (!(sisusb_wait_all_out_complete(sisusb))) |
| return 1; |
| #endif |
| |
| /* Eventually correct endianness */ |
| SISUSB_CORRECT_ENDIANNESS_PACKET(packet); |
| |
| /* 1. send the packet */ |
| ret = sisusb_send_bulk_msg(sisusb, SISUSB_EP_GFX_OUT, len, |
| (char *)packet, NULL, 0, &bytes_transferred, 0, 0); |
| |
| if ((ret == 0) && (len == 6)) { |
| |
| /* 2. if packet len == 6, it means we read, so wait for 32bit |
| * return value and write it to packet->data |
| */ |
| ret = sisusb_recv_bulk_msg(sisusb, SISUSB_EP_GFX_IN, 4, |
| (char *)&tmp, NULL, &bytes_transferred, 0); |
| |
| packet->data = le32_to_cpu(tmp); |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_send_bridge_packet(struct sisusb_usb_data *sisusb, int len, |
| struct sisusb_packet *packet, unsigned int tflags) |
| { |
| int ret; |
| ssize_t bytes_transferred = 0; |
| __le32 tmp; |
| |
| if (len == 6) |
| packet->data = 0; |
| |
| #ifdef SISUSB_DONTSYNC |
| if (!(sisusb_wait_all_out_complete(sisusb))) |
| return 1; |
| #endif |
| |
| /* Eventually correct endianness */ |
| SISUSB_CORRECT_ENDIANNESS_PACKET(packet); |
| |
| /* 1. send the packet */ |
| ret = sisusb_send_bulk_msg(sisusb, SISUSB_EP_BRIDGE_OUT, len, |
| (char *)packet, NULL, 0, &bytes_transferred, tflags, 0); |
| |
| if ((ret == 0) && (len == 6)) { |
| |
| /* 2. if packet len == 6, it means we read, so wait for 32bit |
| * return value and write it to packet->data |
| */ |
| ret = sisusb_recv_bulk_msg(sisusb, SISUSB_EP_BRIDGE_IN, 4, |
| (char *)&tmp, NULL, &bytes_transferred, 0); |
| |
| packet->data = le32_to_cpu(tmp); |
| } |
| |
| return ret; |
| } |
| |
| /* access video memory and mmio (return 0 on success) */ |
| |
| /* Low level */ |
| |
| /* The following routines assume being used to transfer byte, word, |
| * long etc. |
| * This means that |
| * - the write routines expect "data" in machine endianness format. |
| * The data will be converted to leXX in sisusb_xxx_packet. |
| * - the read routines can expect read data in machine-endianess. |
| */ |
| |
| static int sisusb_write_memio_byte(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u8 data) |
| { |
| struct sisusb_packet packet; |
| |
| packet.header = (1 << (addr & 3)) | (type << 6); |
| packet.address = addr & ~3; |
| packet.data = data << ((addr & 3) << 3); |
| return sisusb_send_packet(sisusb, 10, &packet); |
| } |
| |
| static int sisusb_write_memio_word(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u16 data) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| |
| packet.address = addr & ~3; |
| |
| switch (addr & 3) { |
| case 0: |
| packet.header = (type << 6) | 0x0003; |
| packet.data = (u32)data; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 1: |
| packet.header = (type << 6) | 0x0006; |
| packet.data = (u32)data << 8; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 2: |
| packet.header = (type << 6) | 0x000c; |
| packet.data = (u32)data << 16; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 3: |
| packet.header = (type << 6) | 0x0008; |
| packet.data = (u32)data << 24; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| packet.header = (type << 6) | 0x0001; |
| packet.address = (addr & ~3) + 4; |
| packet.data = (u32)data >> 8; |
| ret |= sisusb_send_packet(sisusb, 10, &packet); |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_write_memio_24bit(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u32 data) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| |
| packet.address = addr & ~3; |
| |
| switch (addr & 3) { |
| case 0: |
| packet.header = (type << 6) | 0x0007; |
| packet.data = data & 0x00ffffff; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 1: |
| packet.header = (type << 6) | 0x000e; |
| packet.data = data << 8; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 2: |
| packet.header = (type << 6) | 0x000c; |
| packet.data = data << 16; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| packet.header = (type << 6) | 0x0001; |
| packet.address = (addr & ~3) + 4; |
| packet.data = (data >> 16) & 0x00ff; |
| ret |= sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 3: |
| packet.header = (type << 6) | 0x0008; |
| packet.data = data << 24; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| packet.header = (type << 6) | 0x0003; |
| packet.address = (addr & ~3) + 4; |
| packet.data = (data >> 8) & 0xffff; |
| ret |= sisusb_send_packet(sisusb, 10, &packet); |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_write_memio_long(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u32 data) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| |
| packet.address = addr & ~3; |
| |
| switch (addr & 3) { |
| case 0: |
| packet.header = (type << 6) | 0x000f; |
| packet.data = data; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 1: |
| packet.header = (type << 6) | 0x000e; |
| packet.data = data << 8; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| packet.header = (type << 6) | 0x0001; |
| packet.address = (addr & ~3) + 4; |
| packet.data = data >> 24; |
| ret |= sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 2: |
| packet.header = (type << 6) | 0x000c; |
| packet.data = data << 16; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| packet.header = (type << 6) | 0x0003; |
| packet.address = (addr & ~3) + 4; |
| packet.data = data >> 16; |
| ret |= sisusb_send_packet(sisusb, 10, &packet); |
| break; |
| case 3: |
| packet.header = (type << 6) | 0x0008; |
| packet.data = data << 24; |
| ret = sisusb_send_packet(sisusb, 10, &packet); |
| packet.header = (type << 6) | 0x0007; |
| packet.address = (addr & ~3) + 4; |
| packet.data = data >> 8; |
| ret |= sisusb_send_packet(sisusb, 10, &packet); |
| } |
| |
| return ret; |
| } |
| |
| /* The xxx_bulk routines copy a buffer of variable size. They treat the |
| * buffer as chars, therefore lsb/msb has to be corrected if using the |
| * byte/word/long/etc routines for speed-up |
| * |
| * If data is from userland, set "userbuffer" (and clear "kernbuffer"), |
| * if data is in kernel space, set "kernbuffer" (and clear "userbuffer"); |
| * if neither "kernbuffer" nor "userbuffer" are given, it is assumed |
| * that the data already is in the transfer buffer "sisusb->obuf[index]". |
| */ |
| |
| static int sisusb_write_mem_bulk(struct sisusb_usb_data *sisusb, u32 addr, |
| char *kernbuffer, int length, const char __user *userbuffer, |
| int index, ssize_t *bytes_written) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| static int msgcount; |
| u8 swap8, fromkern = kernbuffer ? 1 : 0; |
| u16 swap16; |
| u32 swap32, flag = (length >> 28) & 1; |
| u8 buf[4]; |
| |
| /* if neither kernbuffer not userbuffer are given, assume |
| * data in obuf |
| */ |
| if (!fromkern && !userbuffer) |
| kernbuffer = sisusb->obuf[index]; |
| |
| (*bytes_written = 0); |
| |
| length &= 0x00ffffff; |
| |
| while (length) { |
| switch (length) { |
| case 1: |
| if (userbuffer) { |
| if (get_user(swap8, (u8 __user *)userbuffer)) |
| return -EFAULT; |
| } else |
| swap8 = kernbuffer[0]; |
| |
| ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM, |
| addr, swap8); |
| |
| if (!ret) |
| (*bytes_written)++; |
| |
| return ret; |
| |
| case 2: |
| if (userbuffer) { |
| if (get_user(swap16, (u16 __user *)userbuffer)) |
| return -EFAULT; |
| } else |
| swap16 = *((u16 *)kernbuffer); |
| |
| ret = sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, |
| addr, swap16); |
| |
| if (!ret) |
| (*bytes_written) += 2; |
| |
| return ret; |
| |
| case 3: |
| if (userbuffer) { |
| if (copy_from_user(&buf, userbuffer, 3)) |
| return -EFAULT; |
| #ifdef __BIG_ENDIAN |
| swap32 = (buf[0] << 16) | |
| (buf[1] << 8) | |
| buf[2]; |
| #else |
| swap32 = (buf[2] << 16) | |
| (buf[1] << 8) | |
| buf[0]; |
| #endif |
| } else |
| #ifdef __BIG_ENDIAN |
| swap32 = (kernbuffer[0] << 16) | |
| (kernbuffer[1] << 8) | |
| kernbuffer[2]; |
| #else |
| swap32 = (kernbuffer[2] << 16) | |
| (kernbuffer[1] << 8) | |
| kernbuffer[0]; |
| #endif |
| |
| ret = sisusb_write_memio_24bit(sisusb, SISUSB_TYPE_MEM, |
| addr, swap32); |
| |
| if (!ret) |
| (*bytes_written) += 3; |
| |
| return ret; |
| |
| case 4: |
| if (userbuffer) { |
| if (get_user(swap32, (u32 __user *)userbuffer)) |
| return -EFAULT; |
| } else |
| swap32 = *((u32 *)kernbuffer); |
| |
| ret = sisusb_write_memio_long(sisusb, SISUSB_TYPE_MEM, |
| addr, swap32); |
| if (!ret) |
| (*bytes_written) += 4; |
| |
| return ret; |
| |
| default: |
| if ((length & ~3) > 0x10000) { |
| |
| packet.header = 0x001f; |
| packet.address = 0x000001d4; |
| packet.data = addr; |
| ret = sisusb_send_bridge_packet(sisusb, 10, |
| &packet, 0); |
| packet.header = 0x001f; |
| packet.address = 0x000001d0; |
| packet.data = (length & ~3); |
| ret |= sisusb_send_bridge_packet(sisusb, 10, |
| &packet, 0); |
| packet.header = 0x001f; |
| packet.address = 0x000001c0; |
| packet.data = flag | 0x16; |
| ret |= sisusb_send_bridge_packet(sisusb, 10, |
| &packet, 0); |
| if (userbuffer) { |
| ret |= sisusb_send_bulk_msg(sisusb, |
| SISUSB_EP_GFX_LBULK_OUT, |
| (length & ~3), |
| NULL, userbuffer, 0, |
| bytes_written, 0, 1); |
| userbuffer += (*bytes_written); |
| } else if (fromkern) { |
| ret |= sisusb_send_bulk_msg(sisusb, |
| SISUSB_EP_GFX_LBULK_OUT, |
| (length & ~3), |
| kernbuffer, NULL, 0, |
| bytes_written, 0, 1); |
| kernbuffer += (*bytes_written); |
| } else { |
| ret |= sisusb_send_bulk_msg(sisusb, |
| SISUSB_EP_GFX_LBULK_OUT, |
| (length & ~3), |
| NULL, NULL, index, |
| bytes_written, 0, 1); |
| kernbuffer += ((*bytes_written) & |
| (sisusb->obufsize-1)); |
| } |
| |
| } else { |
| |
| packet.header = 0x001f; |
| packet.address = 0x00000194; |
| packet.data = addr; |
| ret = sisusb_send_bridge_packet(sisusb, 10, |
| &packet, 0); |
| packet.header = 0x001f; |
| packet.address = 0x00000190; |
| packet.data = (length & ~3); |
| ret |= sisusb_send_bridge_packet(sisusb, 10, |
| &packet, 0); |
| if (sisusb->flagb0 != 0x16) { |
| packet.header = 0x001f; |
| packet.address = 0x00000180; |
| packet.data = flag | 0x16; |
| ret |= sisusb_send_bridge_packet(sisusb, |
| 10, &packet, 0); |
| sisusb->flagb0 = 0x16; |
| } |
| if (userbuffer) { |
| ret |= sisusb_send_bulk_msg(sisusb, |
| SISUSB_EP_GFX_BULK_OUT, |
| (length & ~3), |
| NULL, userbuffer, 0, |
| bytes_written, 0, 1); |
| userbuffer += (*bytes_written); |
| } else if (fromkern) { |
| ret |= sisusb_send_bulk_msg(sisusb, |
| SISUSB_EP_GFX_BULK_OUT, |
| (length & ~3), |
| kernbuffer, NULL, 0, |
| bytes_written, 0, 1); |
| kernbuffer += (*bytes_written); |
| } else { |
| ret |= sisusb_send_bulk_msg(sisusb, |
| SISUSB_EP_GFX_BULK_OUT, |
| (length & ~3), |
| NULL, NULL, index, |
| bytes_written, 0, 1); |
| kernbuffer += ((*bytes_written) & |
| (sisusb->obufsize-1)); |
| } |
| } |
| if (ret) { |
| msgcount++; |
| if (msgcount < 500) |
| dev_err(&sisusb->sisusb_dev->dev, |
| "Wrote %zd of %d bytes, error %d\n", |
| *bytes_written, length, |
| ret); |
| else if (msgcount == 500) |
| dev_err(&sisusb->sisusb_dev->dev, |
| "Too many errors, logging stopped\n"); |
| } |
| addr += (*bytes_written); |
| length -= (*bytes_written); |
| } |
| |
| if (ret) |
| break; |
| |
| } |
| |
| return ret ? -EIO : 0; |
| } |
| |
| /* Remember: Read data in packet is in machine-endianess! So for |
| * byte, word, 24bit, long no endian correction is necessary. |
| */ |
| |
| static int sisusb_read_memio_byte(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u8 *data) |
| { |
| struct sisusb_packet packet; |
| int ret; |
| |
| CLEARPACKET(&packet); |
| packet.header = (1 << (addr & 3)) | (type << 6); |
| packet.address = addr & ~3; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = (u8)(packet.data >> ((addr & 3) << 3)); |
| return ret; |
| } |
| |
| static int sisusb_read_memio_word(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u16 *data) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| |
| CLEARPACKET(&packet); |
| |
| packet.address = addr & ~3; |
| |
| switch (addr & 3) { |
| case 0: |
| packet.header = (type << 6) | 0x0003; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = (u16)(packet.data); |
| break; |
| case 1: |
| packet.header = (type << 6) | 0x0006; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = (u16)(packet.data >> 8); |
| break; |
| case 2: |
| packet.header = (type << 6) | 0x000c; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = (u16)(packet.data >> 16); |
| break; |
| case 3: |
| packet.header = (type << 6) | 0x0008; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = (u16)(packet.data >> 24); |
| packet.header = (type << 6) | 0x0001; |
| packet.address = (addr & ~3) + 4; |
| ret |= sisusb_send_packet(sisusb, 6, &packet); |
| *data |= (u16)(packet.data << 8); |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_read_memio_24bit(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u32 *data) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| |
| packet.address = addr & ~3; |
| |
| switch (addr & 3) { |
| case 0: |
| packet.header = (type << 6) | 0x0007; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data & 0x00ffffff; |
| break; |
| case 1: |
| packet.header = (type << 6) | 0x000e; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data >> 8; |
| break; |
| case 2: |
| packet.header = (type << 6) | 0x000c; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data >> 16; |
| packet.header = (type << 6) | 0x0001; |
| packet.address = (addr & ~3) + 4; |
| ret |= sisusb_send_packet(sisusb, 6, &packet); |
| *data |= ((packet.data & 0xff) << 16); |
| break; |
| case 3: |
| packet.header = (type << 6) | 0x0008; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data >> 24; |
| packet.header = (type << 6) | 0x0003; |
| packet.address = (addr & ~3) + 4; |
| ret |= sisusb_send_packet(sisusb, 6, &packet); |
| *data |= ((packet.data & 0xffff) << 8); |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_read_memio_long(struct sisusb_usb_data *sisusb, int type, |
| u32 addr, u32 *data) |
| { |
| struct sisusb_packet packet; |
| int ret = 0; |
| |
| packet.address = addr & ~3; |
| |
| switch (addr & 3) { |
| case 0: |
| packet.header = (type << 6) | 0x000f; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data; |
| break; |
| case 1: |
| packet.header = (type << 6) | 0x000e; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data >> 8; |
| packet.header = (type << 6) | 0x0001; |
| packet.address = (addr & ~3) + 4; |
| ret |= sisusb_send_packet(sisusb, 6, &packet); |
| *data |= (packet.data << 24); |
| break; |
| case 2: |
| packet.header = (type << 6) | 0x000c; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data >> 16; |
| packet.header = (type << 6) | 0x0003; |
| packet.address = (addr & ~3) + 4; |
| ret |= sisusb_send_packet(sisusb, 6, &packet); |
| *data |= (packet.data << 16); |
| break; |
| case 3: |
| packet.header = (type << 6) | 0x0008; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data >> 24; |
| packet.header = (type << 6) | 0x0007; |
| packet.address = (addr & ~3) + 4; |
| ret |= sisusb_send_packet(sisusb, 6, &packet); |
| *data |= (packet.data << 8); |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_read_mem_bulk(struct sisusb_usb_data *sisusb, u32 addr, |
| char *kernbuffer, int length, char __user *userbuffer, |
| ssize_t *bytes_read) |
| { |
| int ret = 0; |
| char buf[4]; |
| u16 swap16; |
| u32 swap32; |
| |
| (*bytes_read = 0); |
| |
| length &= 0x00ffffff; |
| |
| while (length) { |
| switch (length) { |
| case 1: |
| ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, |
| addr, &buf[0]); |
| if (!ret) { |
| (*bytes_read)++; |
| if (userbuffer) { |
| if (put_user(buf[0], (u8 __user *)userbuffer)) |
| return -EFAULT; |
| } else |
| kernbuffer[0] = buf[0]; |
| } |
| return ret; |
| |
| case 2: |
| ret |= sisusb_read_memio_word(sisusb, SISUSB_TYPE_MEM, |
| addr, &swap16); |
| if (!ret) { |
| (*bytes_read) += 2; |
| if (userbuffer) { |
| if (put_user(swap16, (u16 __user *)userbuffer)) |
| return -EFAULT; |
| } else { |
| *((u16 *)kernbuffer) = swap16; |
| } |
| } |
| return ret; |
| |
| case 3: |
| ret |= sisusb_read_memio_24bit(sisusb, SISUSB_TYPE_MEM, |
| addr, &swap32); |
| if (!ret) { |
| (*bytes_read) += 3; |
| #ifdef __BIG_ENDIAN |
| buf[0] = (swap32 >> 16) & 0xff; |
| buf[1] = (swap32 >> 8) & 0xff; |
| buf[2] = swap32 & 0xff; |
| #else |
| buf[2] = (swap32 >> 16) & 0xff; |
| buf[1] = (swap32 >> 8) & 0xff; |
| buf[0] = swap32 & 0xff; |
| #endif |
| if (userbuffer) { |
| if (copy_to_user(userbuffer, |
| &buf[0], 3)) |
| return -EFAULT; |
| } else { |
| kernbuffer[0] = buf[0]; |
| kernbuffer[1] = buf[1]; |
| kernbuffer[2] = buf[2]; |
| } |
| } |
| return ret; |
| |
| default: |
| ret |= sisusb_read_memio_long(sisusb, SISUSB_TYPE_MEM, |
| addr, &swap32); |
| if (!ret) { |
| (*bytes_read) += 4; |
| if (userbuffer) { |
| if (put_user(swap32, (u32 __user *)userbuffer)) |
| return -EFAULT; |
| |
| userbuffer += 4; |
| } else { |
| *((u32 *)kernbuffer) = swap32; |
| kernbuffer += 4; |
| } |
| addr += 4; |
| length -= 4; |
| } |
| } |
| if (ret) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /* High level: Gfx (indexed) register access */ |
| |
| static int sisusb_setidxreg(struct sisusb_usb_data *sisusb, u32 port, |
| u8 index, u8 data) |
| { |
| int ret; |
| |
| ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, index); |
| ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, data); |
| return ret; |
| } |
| |
| static int sisusb_getidxreg(struct sisusb_usb_data *sisusb, u32 port, |
| u8 index, u8 *data) |
| { |
| int ret; |
| |
| ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, index); |
| ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, data); |
| return ret; |
| } |
| |
| static int sisusb_setidxregandor(struct sisusb_usb_data *sisusb, u32 port, u8 idx, |
| u8 myand, u8 myor) |
| { |
| int ret; |
| u8 tmp; |
| |
| ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, idx); |
| ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, &tmp); |
| tmp &= myand; |
| tmp |= myor; |
| ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, tmp); |
| return ret; |
| } |
| |
| static int sisusb_setidxregmask(struct sisusb_usb_data *sisusb, |
| u32 port, u8 idx, u8 data, u8 mask) |
| { |
| int ret; |
| u8 tmp; |
| |
| ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, idx); |
| ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, &tmp); |
| tmp &= ~(mask); |
| tmp |= (data & mask); |
| ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, tmp); |
| return ret; |
| } |
| |
| static int sisusb_setidxregor(struct sisusb_usb_data *sisusb, u32 port, |
| u8 index, u8 myor) |
| { |
| return sisusb_setidxregandor(sisusb, port, index, 0xff, myor); |
| } |
| |
| static int sisusb_setidxregand(struct sisusb_usb_data *sisusb, u32 port, |
| u8 idx, u8 myand) |
| { |
| return sisusb_setidxregandor(sisusb, port, idx, myand, 0x00); |
| } |
| |
| /* Write/read video ram */ |
| |
| #ifdef SISUSBENDIANTEST |
| static void sisusb_testreadwrite(struct sisusb_usb_data *sisusb) |
| { |
| static u8 srcbuffer[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; |
| char destbuffer[10]; |
| int i, j; |
| |
| sisusb_copy_memory(sisusb, srcbuffer, sisusb->vrambase, 7); |
| |
| for (i = 1; i <= 7; i++) { |
| dev_dbg(&sisusb->sisusb_dev->dev, |
| "sisusb: rwtest %d bytes\n", i); |
| sisusb_read_memory(sisusb, destbuffer, sisusb->vrambase, i); |
| for (j = 0; j < i; j++) { |
| dev_dbg(&sisusb->sisusb_dev->dev, |
| "rwtest read[%d] = %x\n", |
| j, destbuffer[j]); |
| } |
| } |
| } |
| #endif |
| |
| /* access pci config registers (reg numbers 0, 4, 8, etc) */ |
| |
| static int sisusb_write_pci_config(struct sisusb_usb_data *sisusb, |
| int regnum, u32 data) |
| { |
| struct sisusb_packet packet; |
| |
| packet.header = 0x008f; |
| packet.address = regnum | 0x10000; |
| packet.data = data; |
| return sisusb_send_packet(sisusb, 10, &packet); |
| } |
| |
| static int sisusb_read_pci_config(struct sisusb_usb_data *sisusb, |
| int regnum, u32 *data) |
| { |
| struct sisusb_packet packet; |
| int ret; |
| |
| packet.header = 0x008f; |
| packet.address = (u32)regnum | 0x10000; |
| ret = sisusb_send_packet(sisusb, 6, &packet); |
| *data = packet.data; |
| return ret; |
| } |
| |
| /* Clear video RAM */ |
| |
| static int sisusb_clear_vram(struct sisusb_usb_data *sisusb, |
| u32 address, int length) |
| { |
| int ret, i; |
| ssize_t j; |
| |
| if (address < sisusb->vrambase) |
| return 1; |
| |
| if (address >= sisusb->vrambase + sisusb->vramsize) |
| return 1; |
| |
| if (address + length > sisusb->vrambase + sisusb->vramsize) |
| length = sisusb->vrambase + sisusb->vramsize - address; |
| |
| if (length <= 0) |
| return 0; |
| |
| /* allocate free buffer/urb and clear the buffer */ |
| i = sisusb_alloc_outbuf(sisusb); |
| if (i < 0) |
| return -EBUSY; |
| |
| memset(sisusb->obuf[i], 0, sisusb->obufsize); |
| |
| /* We can write a length > buffer size here. The buffer |
| * data will simply be re-used (like a ring-buffer). |
| */ |
| ret = sisusb_write_mem_bulk(sisusb, address, NULL, length, NULL, i, &j); |
| |
| /* Free the buffer/urb */ |
| sisusb_free_outbuf(sisusb, i); |
| |
| return ret; |
| } |
| |
| /* Initialize the graphics core (return 0 on success) |
| * This resets the graphics hardware and puts it into |
| * a defined mode (640x480@60Hz) |
| */ |
| |
| #define GETREG(r, d) sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, r, d) |
| #define SETREG(r, d) sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, r, d) |
| #define SETIREG(r, i, d) sisusb_setidxreg(sisusb, r, i, d) |
| #define GETIREG(r, i, d) sisusb_getidxreg(sisusb, r, i, d) |
| #define SETIREGOR(r, i, o) sisusb_setidxregor(sisusb, r, i, o) |
| #define SETIREGAND(r, i, a) sisusb_setidxregand(sisusb, r, i, a) |
| #define SETIREGANDOR(r, i, a, o) sisusb_setidxregandor(sisusb, r, i, a, o) |
| #define READL(a, d) sisusb_read_memio_long(sisusb, SISUSB_TYPE_MEM, a, d) |
| #define WRITEL(a, d) sisusb_write_memio_long(sisusb, SISUSB_TYPE_MEM, a, d) |
| #define READB(a, d) sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, a, d) |
| #define WRITEB(a, d) sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM, a, d) |
| |
| static int sisusb_triggersr16(struct sisusb_usb_data *sisusb, u8 ramtype) |
| { |
| int ret; |
| u8 tmp8; |
| |
| ret = GETIREG(SISSR, 0x16, &tmp8); |
| if (ramtype <= 1) { |
| tmp8 &= 0x3f; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 |= 0x80; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| } else { |
| tmp8 |= 0xc0; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 &= 0x0f; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 |= 0x80; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 &= 0x0f; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 |= 0xd0; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 &= 0x0f; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| tmp8 |= 0xa0; |
| ret |= SETIREG(SISSR, 0x16, tmp8); |
| } |
| return ret; |
| } |
| |
| static int sisusb_getbuswidth(struct sisusb_usb_data *sisusb, |
| int *bw, int *chab) |
| { |
| int ret; |
| u8 ramtype, done = 0; |
| u32 t0, t1, t2, t3; |
| u32 ramptr = SISUSB_PCI_MEMBASE; |
| |
| ret = GETIREG(SISSR, 0x3a, &ramtype); |
| ramtype &= 3; |
| |
| ret |= SETIREG(SISSR, 0x13, 0x00); |
| |
| if (ramtype <= 1) { |
| ret |= SETIREG(SISSR, 0x14, 0x12); |
| ret |= SETIREGAND(SISSR, 0x15, 0xef); |
| } else { |
| ret |= SETIREG(SISSR, 0x14, 0x02); |
| } |
| |
| ret |= sisusb_triggersr16(sisusb, ramtype); |
| ret |= WRITEL(ramptr + 0, 0x01234567); |
| ret |= WRITEL(ramptr + 4, 0x456789ab); |
| ret |= WRITEL(ramptr + 8, 0x89abcdef); |
| ret |= WRITEL(ramptr + 12, 0xcdef0123); |
| ret |= WRITEL(ramptr + 16, 0x55555555); |
| ret |= WRITEL(ramptr + 20, 0x55555555); |
| ret |= WRITEL(ramptr + 24, 0xffffffff); |
| ret |= WRITEL(ramptr + 28, 0xffffffff); |
| ret |= READL(ramptr + 0, &t0); |
| ret |= READL(ramptr + 4, &t1); |
| ret |= READL(ramptr + 8, &t2); |
| ret |= READL(ramptr + 12, &t3); |
| |
| if (ramtype <= 1) { |
| |
| *chab = 0; *bw = 64; |
| |
| if ((t3 != 0xcdef0123) || (t2 != 0x89abcdef)) { |
| if ((t1 == 0x456789ab) && (t0 == 0x01234567)) { |
| *chab = 0; *bw = 64; |
| ret |= SETIREGAND(SISSR, 0x14, 0xfd); |
| } |
| } |
| if ((t1 != 0x456789ab) || (t0 != 0x01234567)) { |
| *chab = 1; *bw = 64; |
| ret |= SETIREGANDOR(SISSR, 0x14, 0xfc, 0x01); |
| |
| ret |= sisusb_triggersr16(sisusb, ramtype); |
| ret |= WRITEL(ramptr + 0, 0x89abcdef); |
| ret |= WRITEL(ramptr + 4, 0xcdef0123); |
| ret |= WRITEL(ramptr + 8, 0x55555555); |
| ret |= WRITEL(ramptr + 12, 0x55555555); |
| ret |= WRITEL(ramptr + 16, 0xaaaaaaaa); |
| ret |= WRITEL(ramptr + 20, 0xaaaaaaaa); |
| ret |= READL(ramptr + 4, &t1); |
| |
| if (t1 != 0xcdef0123) { |
| *bw = 32; |
| ret |= SETIREGOR(SISSR, 0x15, 0x10); |
| } |
| } |
| |
| } else { |
| |
| *chab = 0; *bw = 64; /* default: cha, bw = 64 */ |
| |
| done = 0; |
| |
| if (t1 == 0x456789ab) { |
| if (t0 == 0x01234567) { |
| *chab = 0; *bw = 64; |
| done = 1; |
| } |
| } else { |
| if (t0 == 0x01234567) { |
| *chab = 0; *bw = 32; |
| ret |= SETIREG(SISSR, 0x14, 0x00); |
| done = 1; |
| } |
| } |
| |
| if (!done) { |
| ret |= SETIREG(SISSR, 0x14, 0x03); |
| ret |= sisusb_triggersr16(sisusb, ramtype); |
| |
| ret |= WRITEL(ramptr + 0, 0x01234567); |
| ret |= WRITEL(ramptr + 4, 0x456789ab); |
| ret |= WRITEL(ramptr + 8, 0x89abcdef); |
| ret |= WRITEL(ramptr + 12, 0xcdef0123); |
| ret |= WRITEL(ramptr + 16, 0x55555555); |
| ret |= WRITEL(ramptr + 20, 0x55555555); |
| ret |= WRITEL(ramptr + 24, 0xffffffff); |
| ret |= WRITEL(ramptr + 28, 0xffffffff); |
| ret |= READL(ramptr + 0, &t0); |
| ret |= READL(ramptr + 4, &t1); |
| |
| if (t1 == 0x456789ab) { |
| if (t0 == 0x01234567) { |
| *chab = 1; *bw = 64; |
| return ret; |
| } /* else error */ |
| } else { |
| if (t0 == 0x01234567) { |
| *chab = 1; *bw = 32; |
| ret |= SETIREG(SISSR, 0x14, 0x01); |
| } /* else error */ |
| } |
| } |
| } |
| return ret; |
| } |
| |
| static int sisusb_verify_mclk(struct sisusb_usb_data *sisusb) |
| { |
| int ret = 0; |
| u32 ramptr = SISUSB_PCI_MEMBASE; |
| u8 tmp1, tmp2, i, j; |
| |
| ret |= WRITEB(ramptr, 0xaa); |
| ret |= WRITEB(ramptr + 16, 0x55); |
| ret |= READB(ramptr, &tmp1); |
| ret |= READB(ramptr + 16, &tmp2); |
| if ((tmp1 != 0xaa) || (tmp2 != 0x55)) { |
| for (i = 0, j = 16; i < 2; i++, j += 16) { |
| ret |= GETIREG(SISSR, 0x21, &tmp1); |
| ret |= SETIREGAND(SISSR, 0x21, (tmp1 & 0xfb)); |
| ret |= SETIREGOR(SISSR, 0x3c, 0x01); /* not on 330 */ |
| ret |= SETIREGAND(SISSR, 0x3c, 0xfe); /* not on 330 */ |
| ret |= SETIREG(SISSR, 0x21, tmp1); |
| ret |= WRITEB(ramptr + 16 + j, j); |
| ret |= READB(ramptr + 16 + j, &tmp1); |
| if (tmp1 == j) { |
| ret |= WRITEB(ramptr + j, j); |
| break; |
| } |
| } |
| } |
| return ret; |
| } |
| |
| static int sisusb_set_rank(struct sisusb_usb_data *sisusb, int *iret, |
| int index, u8 rankno, u8 chab, const u8 dramtype[][5], int bw) |
| { |
| int ret = 0, ranksize; |
| u8 tmp; |
| |
| *iret = 0; |
| |
| if ((rankno == 2) && (dramtype[index][0] == 2)) |
| return ret; |
| |
| ranksize = dramtype[index][3] / 2 * bw / 32; |
| |
| if ((ranksize * rankno) > 128) |
| return ret; |
| |
| tmp = 0; |
| while ((ranksize >>= 1) > 0) |
| tmp += 0x10; |
| |
| tmp |= ((rankno - 1) << 2); |
| tmp |= ((bw / 64) & 0x02); |
| tmp |= (chab & 0x01); |
| |
| ret = SETIREG(SISSR, 0x14, tmp); |
| ret |= sisusb_triggersr16(sisusb, 0); /* sic! */ |
| |
| *iret = 1; |
| |
| return ret; |
| } |
| |
| static int sisusb_check_rbc(struct sisusb_usb_data *sisusb, int *iret, |
| u32 inc, int testn) |
| { |
| int ret = 0, i; |
| u32 j, tmp; |
| |
| *iret = 0; |
| |
| for (i = 0, j = 0; i < testn; i++) { |
| ret |= WRITEL(sisusb->vrambase + j, j); |
| j += inc; |
| } |
| |
| for (i = 0, j = 0; i < testn; i++) { |
| ret |= READL(sisusb->vrambase + j, &tmp); |
| if (tmp != j) |
| return ret; |
| |
| j += inc; |
| } |
| |
| *iret = 1; |
| return ret; |
| } |
| |
| static int sisusb_check_ranks(struct sisusb_usb_data *sisusb, |
| int *iret, int rankno, int idx, int bw, const u8 rtype[][5]) |
| { |
| int ret = 0, i, i2ret; |
| u32 inc; |
| |
| *iret = 0; |
| |
| for (i = rankno; i >= 1; i--) { |
| inc = 1 << (rtype[idx][2] + rtype[idx][1] + rtype[idx][0] + |
| bw / 64 + i); |
| ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 2); |
| if (!i2ret) |
| return ret; |
| } |
| |
| inc = 1 << (rtype[idx][2] + bw / 64 + 2); |
| ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 4); |
| if (!i2ret) |
| return ret; |
| |
| inc = 1 << (10 + bw / 64); |
| ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 2); |
| if (!i2ret) |
| return ret; |
| |
| *iret = 1; |
| return ret; |
| } |
| |
| static int sisusb_get_sdram_size(struct sisusb_usb_data *sisusb, int *iret, |
| int bw, int chab) |
| { |
| int ret = 0, i2ret = 0, i, j; |
| static const u8 sdramtype[13][5] = { |
| { 2, 12, 9, 64, 0x35 }, |
| { 1, 13, 9, 64, 0x44 }, |
| { 2, 12, 8, 32, 0x31 }, |
| { 2, 11, 9, 32, 0x25 }, |
| { 1, 12, 9, 32, 0x34 }, |
| { 1, 13, 8, 32, 0x40 }, |
| { 2, 11, 8, 16, 0x21 }, |
| { 1, 12, 8, 16, 0x30 }, |
| { 1, 11, 9, 16, 0x24 }, |
| { 1, 11, 8, 8, 0x20 }, |
| { 2, 9, 8, 4, 0x01 }, |
| { 1, 10, 8, 4, 0x10 }, |
| { 1, 9, 8, 2, 0x00 } |
| }; |
| |
| *iret = 1; /* error */ |
| |
| for (i = 0; i < 13; i++) { |
| ret |= SETIREGANDOR(SISSR, 0x13, 0x80, sdramtype[i][4]); |
| for (j = 2; j > 0; j--) { |
| ret |= sisusb_set_rank(sisusb, &i2ret, i, j, chab, |
| sdramtype, bw); |
| if (!i2ret) |
| continue; |
| |
| ret |= sisusb_check_ranks(sisusb, &i2ret, j, i, bw, |
| sdramtype); |
| if (i2ret) { |
| *iret = 0; /* ram size found */ |
| return ret; |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int sisusb_setup_screen(struct sisusb_usb_data *sisusb, |
| int clrall, int drwfr) |
| { |
| int ret = 0; |
| u32 address; |
| int i, length, modex, modey, bpp; |
| |
| modex = 640; modey = 480; bpp = 2; |
| |
| address = sisusb->vrambase; /* Clear video ram */ |
| |
| if (clrall) |
| length = sisusb->vramsize; |
| else |
| length = modex * bpp * modey; |
| |
| ret = sisusb_clear_vram(sisusb, address, length); |
| |
| if (!ret && drwfr) { |
| for (i = 0; i < modex; i++) { |
| address = sisusb->vrambase + (i * bpp); |
| ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, |
| address, 0xf100); |
| address += (modex * (modey-1) * bpp); |
| ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, |
| address, 0xf100); |
| } |
| for (i = 0; i < modey; i++) { |
| address = sisusb->vrambase + ((i * modex) * bpp); |
| ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, |
| address, 0xf100); |
| address += ((modex - 1) * bpp); |
| ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, |
| address, 0xf100); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void sisusb_set_default_mode(struct sisusb_usb_data *sisusb, |
| int touchengines) |
| { |
| int i, j, modex, bpp, du; |
| u8 sr31, cr63, tmp8; |
| static const char attrdata[] = { |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, |
| 0x01, 0x00, 0x00, 0x00 |
| }; |
| static const char crtcrdata[] = { |
| 0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e, |
| 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0xea, 0x8c, 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3, |
| 0xff |
| }; |
| static const char grcdata[] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0f, |
| 0xff |
| }; |
| static const char crtcdata[] = { |
| 0x5f, 0x4f, 0x4f, 0x83, 0x55, 0x81, 0x0b, 0x3e, |
| 0xe9, 0x8b, 0xdf, 0xe8, 0x0c, 0x00, 0x00, 0x05, |
| 0x00 |
| }; |
| |
| modex = 640; bpp = 2; |
| |
| GETIREG(SISSR, 0x31, &sr31); |
| GETIREG(SISCR, 0x63, &cr63); |
| SETIREGOR(SISSR, 0x01, 0x20); |
| SETIREG(SISCR, 0x63, cr63 & 0xbf); |
| SETIREGOR(SISCR, 0x17, 0x80); |
| SETIREGOR(SISSR, 0x1f, 0x04); |
| SETIREGAND(SISSR, 0x07, 0xfb); |
| SETIREG(SISSR, 0x00, 0x03); /* seq */ |
| SETIREG(SISSR, 0x01, 0x21); |
| SETIREG(SISSR, 0x02, 0x0f); |
| SETIREG(SISSR, 0x03, 0x00); |
| SETIREG(SISSR, 0x04, 0x0e); |
| SETREG(SISMISCW, 0x23); /* misc */ |
| for (i = 0; i <= 0x18; i++) { /* crtc */ |
| SETIREG(SISCR, i, crtcrdata[i]); |
| } |
| for (i = 0; i <= 0x13; i++) { /* att */ |
| GETREG(SISINPSTAT, &tmp8); |
| SETREG(SISAR, i); |
| SETREG(SISAR, attrdata[i]); |
| } |
| GETREG(SISINPSTAT, &tmp8); |
| SETREG(SISAR, 0x14); |
| SETREG(SISAR, 0x00); |
| GETREG(SISINPSTAT, &tmp8); |
| SETREG(SISAR, 0x20); |
| GETREG(SISINPSTAT, &tmp8); |
| for (i = 0; i <= 0x08; i++) { /* grc */ |
| SETIREG(SISGR, i, grcdata[i]); |
| } |
| SETIREGAND(SISGR, 0x05, 0xbf); |
| for (i = 0x0A; i <= 0x0E; i++) { /* clr ext */ |
| SETIREG(SISSR, i, 0x00); |
| } |
| SETIREGAND(SISSR, 0x37, 0xfe); |
| SETREG(SISMISCW, 0xef); /* sync */ |
| SETIREG(SISCR, 0x11, 0x00); /* crtc */ |
| for (j = 0x00, i = 0; i <= 7; i++, j++) |
| SETIREG(SISCR, j, crtcdata[i]); |
| |
| for (j = 0x10; i <= 10; i++, j++) |
| SETIREG(SISCR, j, crtcdata[i]); |
| |
| for (j = 0x15; i <= 12; i++, j++) |
| SETIREG(SISCR, j, crtcdata[i]); |
| |
| for (j = 0x0A; i <= 15; i++, j++) |
| SETIREG(SISSR, j, crtcdata[i]); |
| |
| SETIREG(SISSR, 0x0E, (crtcdata[16] & 0xE0)); |
| SETIREGANDOR(SISCR, 0x09, 0x5f, ((crtcdata[16] & 0x01) << 5)); |
| SETIREG(SISCR, 0x14, 0x4f); |
| du = (modex / 16) * (bpp * 2); /* offset/pitch */ |
| SETIREGANDOR(SISSR, 0x0e, 0xf0, ((du >> 8) & 0x0f)); |
| SETIREG(SISCR, 0x13, (du & 0xff)); |
| du <<= 5; |
| tmp8 = du >> 8; |
| SETIREG(SISSR, 0x10, tmp8); |
| SETIREG(SISSR, 0x31, 0x00); /* VCLK */ |
| SETIREG(SISSR, 0x2b, 0x1b); |
| SETIREG(SISSR, 0x2c, 0xe1); |
| SETIREG(SISSR, 0x2d, 0x01); |
| SETIREGAND(SISSR, 0x3d, 0xfe); /* FIFO */ |
| SETIREG(SISSR, 0x08, 0xae); |
| SETIREGAND(SISSR, 0x09, 0xf0); |
| SETIREG(SISSR, 0x08, 0x34); |
| SETIREGOR(SISSR, 0x3d, 0x01); |
| SETIREGAND(SISSR, 0x1f, 0x3f); /* mode regs */ |
| SETIREGANDOR(SISSR, 0x06, 0xc0, 0x0a); |
| SETIREG(SISCR, 0x19, 0x00); |
| SETIREGAND(SISCR, 0x1a, 0xfc); |
| SETIREGAND(SISSR, 0x0f, 0xb7); |
| SETIREGAND(SISSR, 0x31, 0xfb); |
| SETIREGANDOR(SISSR, 0x21, 0x1f, 0xa0); |
| SETIREGAND(SISSR, 0x32, 0xf3); |
| SETIREGANDOR(SISSR, 0x07, 0xf8, 0x03); |
| SETIREG(SISCR, 0x52, 0x6c); |
| |
| SETIREG(SISCR, 0x0d, 0x00); /* adjust frame */ |
| SETIREG(SISCR, 0x0c, 0x00); |
| SETIREG(SISSR, 0x0d, 0x00); |
| SETIREGAND(SISSR, 0x37, 0xfe); |
| |
| SETIREG(SISCR, 0x32, 0x20); |
| SETIREGAND(SISSR, 0x01, 0xdf); /* enable display */ |
| SETIREG(SISCR, 0x63, (cr63 & 0xbf)); |
| SETIREG(SISSR, 0x31, (sr31 & 0xfb)); |
| |
| if (touchengines) { |
| SETIREG(SISSR, 0x20, 0xa1); /* enable engines */ |
| SETIREGOR(SISSR, 0x1e, 0x5a); |
| |
| SETIREG(SISSR, 0x26, 0x01); /* disable cmdqueue */ |
| SETIREG(SISSR, 0x27, 0x1f); |
| SETIREG(SISSR, 0x26, 0x00); |
| } |
| |
| SETIREG(SISCR, 0x34, 0x44); /* we just set std mode #44 */ |
| } |
| |
| static int sisusb_init_gfxcore(struct sisusb_usb_data *sisusb) |
| { |
| int ret = 0, i, j, bw, chab, iret, retry = 3; |
| u8 tmp8, ramtype; |
| u32 tmp32; |
| static const char mclktable[] = { |
| 0x3b, 0x22, 0x01, 143, |
| 0x3b, 0x22, 0x01, 143, |
| 0x3b, 0x22, 0x01, 143, |
| 0x3b, 0x22, 0x01, 143 |
| }; |
| static const char eclktable[] = { |
| 0x3b, 0x22, 0x01, 143, |
| 0x3b, 0x22, 0x01, 143, |
| 0x3b, 0x22, 0x01, 143, |
| 0x3b, 0x22, 0x01, 143 |
| }; |
| static const char ramtypetable1[] = { |
| 0x00, 0x04, 0x60, 0x60, |
| 0x0f, 0x0f, 0x1f, 0x1f, |
| 0xba, 0xba, 0xba, 0xba, |
| 0xa9, 0xa9, 0xac, 0xac, |
| 0xa0, 0xa0, 0xa0, 0xa8, |
| 0x00, 0x00, 0x02, 0x02, |
| 0x30, 0x30, 0x40, 0x40 |
| }; |
| static const char ramtypetable2[] = { |
| 0x77, 0x77, 0x44, 0x44, |
| 0x77, 0x77, 0x44, 0x44, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x5b, 0x5b, 0xab, 0xab, |
| 0x00, 0x00, 0xf0, 0xf8 |
| }; |
| |
| while (retry--) { |
| |
| /* Enable VGA */ |
| ret = GETREG(SISVGAEN, &tmp8); |
| ret |= SETREG(SISVGAEN, (tmp8 | 0x01)); |
| |
| /* Enable GPU access to VRAM */ |
| ret |= GETREG(SISMISCR, &tmp8); |
| ret |= SETREG(SISMISCW, (tmp8 | 0x01)); |
| |
| if (ret) |
| continue; |
| |
| /* Reset registers */ |
| ret |= SETIREGAND(SISCR, 0x5b, 0xdf); |
| ret |= SETIREG(SISSR, 0x05, 0x86); |
| ret |= SETIREGOR(SISSR, 0x20, 0x01); |
| |
| ret |= SETREG(SISMISCW, 0x67); |
| |
| for (i = 0x06; i <= 0x1f; i++) |
| ret |= SETIREG(SISSR, i, 0x00); |
| |
| for (i = 0x21; i <= 0x27; i++) |
| ret |= SETIREG(SISSR, i, 0x00); |
| |
| for (i = 0x31; i <= 0x3d; i++) |
| ret |= SETIREG(SISSR, i, 0x00); |
| |
| for (i = 0x12; i <= 0x1b; i++) |
| ret |= SETIREG(SISSR, i, 0x00); |
| |
| for (i = 0x79; i <= 0x7c; i++) |
| ret |= SETIREG(SISCR, i, 0x00); |
| |
| if (ret) |
| continue; |
| |
| ret |= SETIREG(SISCR, 0x63, 0x80); |
| |
| ret |= GETIREG(SISSR, 0x3a, &ramtype); |
| ramtype &= 0x03; |
| |
| ret |= SETIREG(SISSR, 0x28, mclktable[ramtype * 4]); |
| ret |= SETIREG(SISSR, 0x29, mclktable[(ramtype * 4) + 1]); |
| ret |= SETIREG(SISSR, 0x2a, mclktable[(ramtype * 4) + 2]); |
| |
| ret |= SETIREG(SISSR, 0x2e, eclktable[ramtype * 4]); |
| ret |= SETIREG(SISSR, 0x2f, eclktable[(ramtype * 4) + 1]); |
| ret |= SETIREG(SISSR, 0x30, eclktable[(ramtype * 4) + 2]); |
| |
| ret |= SETIREG(SISSR, 0x07, 0x18); |
| ret |= SETIREG(SISSR, 0x11, 0x0f); |
| |
| if (ret) |
| continue; |
| |
| for (i = 0x15, j = 0; i <= 0x1b; i++, j++) { |
| ret |= SETIREG(SISSR, i, |
| ramtypetable1[(j*4) + ramtype]); |
| } |
| for (i = 0x40, j = 0; i <= 0x44; i++, j++) { |
| ret |= SETIREG(SISCR, i, |
| ramtypetable2[(j*4) + ramtype]); |
| } |
| |
| ret |= SETIREG(SISCR, 0x49, 0xaa); |
| |
| ret |= SETIREG(SISSR, 0x1f, 0x00); |
| ret |= SETIREG(SISSR, 0x20, 0xa0); |
| ret |= SETIREG(SISSR, 0x23, 0xf6); |
| ret |= SETIREG(SISSR, 0x24, 0x0d); |
| ret |= SETIREG(SISSR, 0x25, 0x33); |
| |
| ret |= SETIREG(SISSR, 0x11, 0x0f); |
| |
| ret |= SETIREGOR(SISPART1, 0x2f, 0x01); |
| |
| ret |= SETIREGAND(SISCAP, 0x3f, 0xef); |
| |
| if (ret) |
| continue; |
| |
| ret |= SETIREG(SISPART1, 0x00, 0x00); |
| |
| ret |= GETIREG(SISSR, 0x13, &tmp8); |
| tmp8 >>= 4; |
| |
| ret |= SETIREG(SISPART1, 0x02, 0x00); |
| ret |= SETIREG(SISPART1, 0x2e, 0x08); |
| |
| ret |= sisusb_read_pci_config(sisusb, 0x50, &tmp32); |
| tmp32 &= 0x00f00000; |
| tmp8 = (tmp32 == 0x100000) ? 0x33 : 0x03; |
| ret |= SETIREG(SISSR, 0x25, tmp8); |
| tmp8 = (tmp32 == 0x100000) ? 0xaa : 0x88; |
| ret |= SETIREG(SISCR, 0x49, tmp8); |
| |
| ret |= SETIREG(SISSR, 0x27, 0x1f); |
| ret |= SETIREG(SISSR, 0x31, 0x00); |
| ret |= SETIREG(SISSR, 0x32, 0x11); |
| ret |= SETIREG(SISSR, 0x33, 0x00); |
| |
| if (ret) |
| continue; |
| |
| ret |= SETIREG(SISCR, 0x83, 0x00); |
| |
| sisusb_set_default_mode(sisusb, 0); |
| |
| ret |= SETIREGAND(SISSR, 0x21, 0xdf); |
| ret |= SETIREGOR(SISSR, 0x01, 0x20); |
| ret |= SETIREGOR(SISSR, 0x16, 0x0f); |
| |
| ret |= sisusb_triggersr16(sisusb, ramtype); |
| |
| /* Disable refresh */ |
| ret |= SETIREGAND(SISSR, 0x17, 0xf8); |
| ret |= SETIREGOR(SISSR, 0x19, 0x03); |
| |
| ret |= sisusb_getbuswidth(sisusb, &bw, &chab); |
| ret |= sisusb_verify_mclk(sisusb); |
| |
| if (ramtype <= 1) { |
| ret |= sisusb_get_sdram_size(sisusb, &iret, bw, chab); |
| if (iret) { |
| dev_err(&sisusb->sisusb_dev->dev, |
| "RAM size detection failed, assuming 8MB video RAM\n"); |
| ret |= SETIREG(SISSR, 0x14, 0x31); |
| /* TODO */ |
| } |
| } else { |
| dev_err(&sisusb->sisusb_dev->dev, |
| "DDR RAM device found, assuming 8MB video RAM\n"); |
| ret |= SETIREG(SISSR, 0x14, 0x31); |
| /* *** TODO *** */ |
| } |
| |
| /* Enable refresh */ |
| ret |= SETIREG(SISSR, 0x16, ramtypetable1[4 + ramtype]); |
| ret |= SETIREG(SISSR, 0x17, ramtypetable1[8 + ramtype]); |
| ret |= SETIREG(SISSR, 0x19, ramtypetable1[16 + ramtype]); |
| |
| ret |= SETIREGOR(SISSR, 0x21, 0x20); |
| |
| ret |= SETIREG(SISSR, 0x22, 0xfb); |
| ret |= SETIREG(SISSR, 0x21, 0xa5); |
| |
| if (ret == 0) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| #undef SETREG |
| #undef GETREG |
| #undef SETIREG |
| #undef GETIREG |
| #undef SETIREGOR |
| #undef SETIREGAND |
| #undef SETIREGANDOR |
| #undef READL |
| #undef WRITEL |
| |
| static void sisusb_get_ramconfig(struct sisusb_usb_data *sisusb) |
| { |
| u8 tmp8, tmp82, ramtype; |
| int bw = 0; |
| char *ramtypetext1 = NULL; |
| static const char ram_datarate[4] = {'S', 'S', 'D', 'D'}; |
| static const char ram_dynamictype[4] = {'D', 'G', 'D', 'G'}; |
| static const int busSDR[4] = {64, 64, 128, 128}; |
| static const int busDDR[4] = {32, 32, 64, 64}; |
| static const int busDDRA[4] = {64+32, 64+32, (64+32)*2, (64+32)*2}; |
| |
| sisusb_getidxreg(sisusb, SISSR, 0x14, &tmp8); |
| sisusb_getidxreg(sisusb, SISSR, 0x15, &tmp82); |
| sisusb_getidxreg(sisusb, SISSR, 0x3a, &ramtype); |
| sisusb->vramsize = (1 << ((tmp8 & 0xf0) >> 4)) * 1024 * 1024; |
| ramtype &= 0x03; |
| switch ((tmp8 >> 2) & 0x03) { |
| case 0: |
| ramtypetext1 = "1 ch/1 r"; |
| if (tmp82 & 0x10) |
| bw = 32; |
| else |
| bw = busSDR[(tmp8 & 0x03)]; |
| |
| break; |
| case 1: |
| ramtypetext1 = "1 ch/2 r"; |
| sisusb->vramsize <<= 1; |
| bw = busSDR[(tmp8 & 0x03)]; |
| break; |
| case 2: |
| ramtypetext1 = "asymmetric"; |
| sisusb->vramsize += sisusb->vramsize/2; |
| bw = busDDRA[(tmp8 & 0x03)]; |
| break; |
| case 3: |
| ramtypetext1 = "2 channel"; |
| sisusb->vramsize <<= 1; |
| bw = busDDR[(tmp8 & 0x03)]; |
| break; |
| } |
| |
| dev_info(&sisusb->sisusb_dev->dev, |
| "%dMB %s %cDR S%cRAM, bus width %d\n", |
| sisusb->vramsize >> 20, ramtypetext1, |
| ram_datarate[ramtype], ram_dynamictype[ramtype], bw); |
| } |
| |
| static int sisusb_do_init_gfxdevice(struct sisusb_usb_data *sisusb) |
| { |
| struct sisusb_packet packet; |
| int ret; |
| u32 tmp32; |
| |
| /* Do some magic */ |
| packet.header = 0x001f; |
| packet.address = 0x00000324; |
| packet.data = 0x00000004; |
| ret = sisusb_send_bridge_packet(sisusb, 10, &packet, 0); |
| |
| packet.header = 0x001f; |
| packet.address = 0x00000364; |
| packet.data = 0x00000004; |
| ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); |
| |
| packet.header = 0x001f; |
| packet.address = 0x00000384; |
| packet.data = 0x00000004; |
| ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); |
| |
| packet.header = 0x001f; |
| packet.address = 0x00000100; |
| packet.data = 0x00000700; |
| ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); |
| |
| packet.header = 0x000f; |
| packet.address = 0x00000004; |
| ret |= sisusb_send_bridge_packet(sisusb, 6, &packet, 0); |
| packet.data |= 0x17; |
| ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); |
| |
| /* Init BAR 0 (VRAM) */ |
| ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32); |
| ret |= sisusb_write_pci_config(sisusb, 0x10, 0xfffffff0); |
| ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32); |
| tmp32 &= 0x0f; |
| tmp32 |= SISUSB_PCI_MEMBASE; |
| ret |= sisusb_write_pci_config(sisusb, 0x10, tmp32); |
| |
| /* Init BAR 1 (MMIO) */ |
| ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32); |
| ret |= sisusb_write_pci_config(sisusb, 0x14, 0xfffffff0); |
| ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32); |
| tmp32 &= 0x0f; |
| tmp32 |= SISUSB_PCI_MMIOBASE; |
| ret |= sisusb_write_pci_config(sisusb, 0x14, tmp32); |
| |
| /* Init BAR 2 (i/o ports) */ |
| ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32); |
| ret |= sisusb_write_pci_config(sisusb, 0x18, 0xfffffff0); |
| ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32); |
| tmp32 &= 0x0f; |
| tmp32 |= SISUSB_PCI_IOPORTBASE; |
| ret |= sisusb_write_pci_config(sisusb, 0x18, tmp32); |
| |
| /* Enable memory and i/o access */ |
| ret |= sisusb_read_pci_config(sisusb, 0x04, &tmp32); |
| tmp32 |= 0x3; |
| ret |= sisusb_write_pci_config(sisusb, 0x04, tmp32); |
| |
| if (ret == 0) { |
| /* Some further magic */ |
| packet.header = 0x001f; |
| packet.address = 0x00000050; |
| packet.data = 0x000000ff; |
| ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); |
| } |
| |
| return ret; |
| } |
| |
| /* Initialize the graphics device (return 0 on success) |
| * This initializes the net2280 as well as the PCI registers |
| * of the graphics board. |
| */ |
| |
| static int sisusb_init_gfxdevice(struct sisusb_usb_data *sisusb, int initscreen) |
| { |
| int ret = 0, test = 0; |
| u32 tmp32; |
| |
| if (sisusb->devinit == 1) { |
| /* Read PCI BARs and see if they have been set up */ |
| ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32); |
| if (ret) |
| return ret; |
| |
| if ((tmp32 & 0xfffffff0) == SISUSB_PCI_MEMBASE) |
| test++; |
| |
| ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32); |
| if (ret) |
| return ret; |
| |
| if ((tmp32 & 0xfffffff0) == SISUSB_PCI_MMIOBASE) |
| test++; |
| |
| ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32); |
| if (ret) |
| return ret; |
| |
| if ((tmp32 & 0xfffffff0) == SISUSB_PCI_IOPORTBASE) |
| test++; |
| } |
| |
| /* No? So reset the device */ |
| if ((sisusb->devinit == 0) || (test != 3)) { |
| |
| ret |= sisusb_do_init_gfxdevice(sisusb); |
| |
| if (ret == 0) |
| sisusb->devinit = 1; |
| |
| } |
| |
| if (sisusb->devinit) { |
| /* Initialize the graphics core */ |
| if (sisusb_init_gfxcore(sisusb) == 0) { |
| sisusb->gfxinit = 1; |
| sisusb_get_ramconfig(sisusb); |
| sisusb_set_default_mode(sisusb, 1); |
| ret |= sisusb_setup_screen(sisusb, 1, initscreen); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* fops */ |
| |
| static int sisusb_open(struct inode *inode, struct file *file) |
| { |
| struct sisusb_usb_data *sisusb; |
| struct usb_interface *interface; |
| int subminor = iminor(inode); |
| |
| interface = usb_find_interface(&sisusb_driver, subminor); |
| if (!interface) |
| return -ENODEV; |
| |
| sisusb = usb_get_intfdata(interface); |
| if (!sisusb) |
| return -ENODEV; |
| |
| mutex_lock(&sisusb->lock); |
| |
| if (!sisusb->present || !sisusb->ready) { |
| mutex_unlock(&sisusb->lock); |
| return -ENODEV; |
| } |
| |
| if (sisusb->isopen) { |
| mutex_unlock(&sisusb->lock); |
| return -EBUSY; |
| } |
| |
| if (!sisusb->devinit) { |
| if (sisusb->sisusb_dev->speed == USB_SPEED_HIGH || |
| sisusb->sisusb_dev->speed >= USB_SPEED_SUPER) { |
| if (sisusb_init_gfxdevice(sisusb, 0)) { |
| mutex_unlock(&sisusb->lock); |
| dev_err(&sisusb->sisusb_dev->dev, |
| "Failed to initialize device\n"); |
| return -EIO; |
| } |
| } else { |
| mutex_unlock(&sisusb->lock); |
| dev_err(&sisusb->sisusb_dev->dev, |
| "Device not attached to USB 2.0 hub\n"); |
| return -EIO; |
| } |
| } |
| |
| /* Increment usage count for our sisusb */ |
| kref_get(&sisusb->kref); |
| |
| sisusb->isopen = 1; |
| |
| file->private_data = sisusb; |
| |
| mutex_unlock(&sisusb->lock); |
| |
| return 0; |
| } |
| |
| static void sisusb_delete(struct kref *kref) |
| { |
| struct sisusb_usb_data *sisusb = to_sisusb_dev(kref); |
| |
| if (!sisusb) |
| return; |
| |
| usb_put_dev(sisusb->sisusb_dev); |
| |
| sisusb->sisusb_dev = NULL; |
| sisusb_free_buffers(sisusb); |
| sisusb_free_urbs(sisusb); |
| kfree(sisusb); |
| } |
| |
| static int sisusb_release(struct inode *inode, struct file *file) |
| { |
| struct sisusb_usb_data *sisusb; |
| |
| sisusb = file->private_data; |
| if (!sisusb) |
| return -ENODEV; |
| |
| mutex_lock(&sisusb->lock); |
| |
| if (sisusb->present) { |
| /* Wait for all URBs to finish if device still present */ |
| if (!sisusb_wait_all_out_complete(sisusb)) |
| sisusb_kill_all_busy(sisusb); |
| } |
| |
| sisusb->isopen = 0; |
| file->private_data = NULL; |
| |
| mutex_unlock(&sisusb->lock); |
| |
| /* decrement the usage count on our device */ |
| kref_put(&sisusb->kref, sisusb_delete); |
| |
| return 0; |
| } |
| |
| static ssize_t sisusb_read(struct file *file, char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct sisusb_usb_data *sisusb; |
| ssize_t bytes_read = 0; |
| int errno = 0; |
| u8 buf8; |
| u16 buf16; |
| u32 buf32, address; |
| |
| sisusb = file->private_data; |
| if (!sisusb) |
| return -ENODEV; |
| |
| mutex_lock(&sisusb->lock); |
| |
| /* Sanity check */ |
| if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { |
| mutex_unlock(&sisusb->lock); |
| return -ENODEV; |
| } |
| |
| if ((*ppos) >= SISUSB_PCI_PSEUDO_IOPORTBASE && |
| (*ppos) < SISUSB_PCI_PSEUDO_IOPORTBASE + 128) { |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_IOPORTBASE + |
| SISUSB_PCI_IOPORTBASE; |
| |
| /* Read i/o ports |
| * Byte, word and long(32) can be read. As this |
| * emulates inX instructions, the data returned is |
| * in machine-endianness. |
| */ |
| switch (count) { |
| case 1: |
| if (sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, |
| address, &buf8)) |
| errno = -EIO; |
| else if (put_user(buf8, (u8 __user *)buffer)) |
| errno = -EFAULT; |
| else |
| bytes_read = 1; |
| |
| break; |
| |
| case 2: |
| if (sisusb_read_memio_word(sisusb, SISUSB_TYPE_IO, |
| address, &buf16)) |
| errno = -EIO; |
| else if (put_user(buf16, (u16 __user *)buffer)) |
| errno = -EFAULT; |
| else |
| bytes_read = 2; |
| |
| break; |
| |
| case 4: |
| if (sisusb_read_memio_long(sisusb, SISUSB_TYPE_IO, |
| address, &buf32)) |
| errno = -EIO; |
| else if (put_user(buf32, (u32 __user *)buffer)) |
| errno = -EFAULT; |
| else |
| bytes_read = 4; |
| |
| break; |
| |
| default: |
| errno = -EIO; |
| |
| } |
| |
| } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MEMBASE && (*ppos) < |
| SISUSB_PCI_PSEUDO_MEMBASE + sisusb->vramsize) { |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_MEMBASE + |
| SISUSB_PCI_MEMBASE; |
| |
| /* Read video ram |
| * Remember: Data delivered is never endian-corrected |
| */ |
| errno = sisusb_read_mem_bulk(sisusb, address, |
| NULL, count, buffer, &bytes_read); |
| |
| if (bytes_read) |
| errno = bytes_read; |
| |
| } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MMIOBASE && |
| (*ppos) < SISUSB_PCI_PSEUDO_MMIOBASE + |
| SISUSB_PCI_MMIOSIZE) { |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_MMIOBASE + |
| SISUSB_PCI_MMIOBASE; |
| |
| /* Read MMIO |
| * Remember: Data delivered is never endian-corrected |
| */ |
| errno = sisusb_read_mem_bulk(sisusb, address, |
| NULL, count, buffer, &bytes_read); |
| |
| if (bytes_read) |
| errno = bytes_read; |
| |
| } else if ((*ppos) >= SISUSB_PCI_PSEUDO_PCIBASE && |
| (*ppos) <= SISUSB_PCI_PSEUDO_PCIBASE + 0x5c) { |
| |
| if (count != 4) { |
| mutex_unlock(&sisusb->lock); |
| return -EINVAL; |
| } |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_PCIBASE; |
| |
| /* Read PCI config register |
| * Return value delivered in machine endianness. |
| */ |
| if (sisusb_read_pci_config(sisusb, address, &buf32)) |
| errno = -EIO; |
| else if (put_user(buf32, (u32 __user *)buffer)) |
| errno = -EFAULT; |
| else |
| bytes_read = 4; |
| |
| } else { |
| |
| errno = -EBADFD; |
| |
| } |
| |
| (*ppos) += bytes_read; |
| |
| mutex_unlock(&sisusb->lock); |
| |
| return errno ? errno : bytes_read; |
| } |
| |
| static ssize_t sisusb_write(struct file *file, const char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct sisusb_usb_data *sisusb; |
| int errno = 0; |
| ssize_t bytes_written = 0; |
| u8 buf8; |
| u16 buf16; |
| u32 buf32, address; |
| |
| sisusb = file->private_data; |
| if (!sisusb) |
| return -ENODEV; |
| |
| mutex_lock(&sisusb->lock); |
| |
| /* Sanity check */ |
| if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { |
| mutex_unlock(&sisusb->lock); |
| return -ENODEV; |
| } |
| |
| if ((*ppos) >= SISUSB_PCI_PSEUDO_IOPORTBASE && |
| (*ppos) < SISUSB_PCI_PSEUDO_IOPORTBASE + 128) { |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_IOPORTBASE + |
| SISUSB_PCI_IOPORTBASE; |
| |
| /* Write i/o ports |
| * Byte, word and long(32) can be written. As this |
| * emulates outX instructions, the data is expected |
| * in machine-endianness. |
| */ |
| switch (count) { |
| case 1: |
| if (get_user(buf8, (u8 __user *)buffer)) |
| errno = -EFAULT; |
| else if (sisusb_write_memio_byte(sisusb, |
| SISUSB_TYPE_IO, address, buf8)) |
| errno = -EIO; |
| else |
| bytes_written = 1; |
| |
| break; |
| |
| case 2: |
| if (get_user(buf16, (u16 __user *)buffer)) |
| errno = -EFAULT; |
| else if (sisusb_write_memio_word(sisusb, |
| SISUSB_TYPE_IO, address, buf16)) |
| errno = -EIO; |
| else |
| bytes_written = 2; |
| |
| break; |
| |
| case 4: |
| if (get_user(buf32, (u32 __user *)buffer)) |
| errno = -EFAULT; |
| else if (sisusb_write_memio_long(sisusb, |
| SISUSB_TYPE_IO, address, buf32)) |
| errno = -EIO; |
| else |
| bytes_written = 4; |
| |
| break; |
| |
| default: |
| errno = -EIO; |
| } |
| |
| } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MEMBASE && |
| (*ppos) < SISUSB_PCI_PSEUDO_MEMBASE + |
| sisusb->vramsize) { |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_MEMBASE + |
| SISUSB_PCI_MEMBASE; |
| |
| /* Write video ram. |
| * Buffer is copied 1:1, therefore, on big-endian |
| * machines, the data must be swapped by userland |
| * in advance (if applicable; no swapping in 8bpp |
| * mode or if YUV data is being transferred). |
| */ |
| errno = sisusb_write_mem_bulk(sisusb, address, NULL, |
| count, buffer, 0, &bytes_written); |
| |
| if (bytes_written) |
| errno = bytes_written; |
| |
| } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MMIOBASE && |
| (*ppos) < SISUSB_PCI_PSEUDO_MMIOBASE + |
| SISUSB_PCI_MMIOSIZE) { |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_MMIOBASE + |
| SISUSB_PCI_MMIOBASE; |
| |
| /* Write MMIO. |
| * Buffer is copied 1:1, therefore, on big-endian |
| * machines, the data must be swapped by userland |
| * in advance. |
| */ |
| errno = sisusb_write_mem_bulk(sisusb, address, NULL, |
| count, buffer, 0, &bytes_written); |
| |
| if (bytes_written) |
| errno = bytes_written; |
| |
| } else if ((*ppos) >= SISUSB_PCI_PSEUDO_PCIBASE && |
| (*ppos) <= SISUSB_PCI_PSEUDO_PCIBASE + |
| SISUSB_PCI_PCONFSIZE) { |
| |
| if (count != 4) { |
| mutex_unlock(&sisusb->lock); |
| return -EINVAL; |
| } |
| |
| address = (*ppos) - SISUSB_PCI_PSEUDO_PCIBASE; |
| |
| /* Write PCI config register. |
| * Given value expected in machine endianness. |
| */ |
| if (get_user(buf32, (u32 __user *)buffer)) |
| errno = -EFAULT; |
| else if (sisusb_write_pci_config(sisusb, address, buf32)) |
| errno = -EIO; |
| else |
| bytes_written = 4; |
| |
| |
| } else { |
| |
| /* Error */ |
| errno = -EBADFD; |
| |
| } |
| |
| (*ppos) += bytes_written; |
| |
| mutex_unlock(&sisusb->lock); |
| |
| return errno ? errno : bytes_written; |
| } |
| |
| static loff_t sisusb_lseek(struct file *file, loff_t offset, int orig) |
| { |
| struct sisusb_usb_data *sisusb; |
| loff_t ret; |
| |
| sisusb = file->private_data; |
| if (!sisusb) |
| return -ENODEV; |
| |
| mutex_lock(&sisusb->lock); |
| |
| /* Sanity check */ |
| if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { |
| mutex_unlock(&sisusb->lock); |
| return -ENODEV; |
| } |
| |
| ret = no_seek_end_llseek(file, offset, orig); |
| |
| mutex_unlock(&sisusb->lock); |
| return ret; |
| } |
| |
| static int sisusb_handle_command(struct sisusb_usb_data *sisusb, |
| struct sisusb_command *y, unsigned long arg) |
| { |
| int retval, length; |
| u32 port, address; |
| |
| /* All our commands require the device |
| * to be initialized. |
| */ |
| if (!sisusb->devinit) |
| return -ENODEV; |
| |
| port = y->data3 - |
| SISUSB_PCI_PSEUDO_IOPORTBASE + |
| SISUSB_PCI_IOPORTBASE; |
| |
| switch (y->operation) { |
| case SUCMD_GET: |
| retval = sisusb_getidxreg(sisusb, port, y->data0, &y->data1); |
| if (!retval) { |
| if (copy_to_user((void __user *)arg, y, sizeof(*y))) |
| retval = -EFAULT; |
| } |
| break; |
| |
| case SUCMD_SET: |
| retval = sisusb_setidxreg(sisusb, port, y->data0, y->data1); |
| break; |
| |
| case SUCMD_SETOR: |
| retval = sisusb_setidxregor(sisusb, port, y->data0, y->data1); |
| break; |
| |
| case SUCMD_SETAND: |
| retval = sisusb_setidxregand(sisusb, port, y->data0, y->data1); |
| break; |
| |
| case SUCMD_SETANDOR: |
| retval = sisusb_setidxregandor(sisusb, port, y->data0, |
| y->data1, y->data2); |
| break; |
| |
| case SUCMD_SETMASK: |
| retval = sisusb_setidxregmask(sisusb, port, y->data0, |
| y->data1, y->data2); |
| break; |
| |
| case SUCMD_CLRSCR: |
| /* Gfx core must be initialized */ |
| if (!sisusb->gfxinit) |
| return -ENODEV; |
| |
| length = (y->data0 << 16) | (y->data1 << 8) | y->data2; |
| address = y->data3 - SISUSB_PCI_PSEUDO_MEMBASE + |
| SISUSB_PCI_MEMBASE; |
| retval = sisusb_clear_vram(sisusb, address, length); |
| break; |
| |
| case SUCMD_HANDLETEXTMODE: |
| retval = 0; |
| break; |
| |
| default: |
| retval = -EINVAL; |
| } |
| |
| if (retval > 0) |
| retval = -EIO; |
| |
| return retval; |
| } |
| |
| static long sisusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| struct sisusb_usb_data *sisusb; |
| struct sisusb_info x; |
| struct sisusb_command y; |
| long retval = 0; |
| u32 __user *argp = (u32 __user *)arg; |
| |
| sisusb = file->private_data; |
| if (!sisusb) |
| return -ENODEV; |
| |
| mutex_lock(&sisusb->lock); |
| |
| /* Sanity check */ |
| if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { |
| retval = -ENODEV; |
| goto err_out; |
| } |
| |
| switch (cmd) { |
| case SISUSB_GET_CONFIG_SIZE: |
| |
| if (put_user(sizeof(x), argp)) |
| retval = -EFAULT; |
| |
| break; |
| |
| case SISUSB_GET_CONFIG: |
| |
| x.sisusb_id = SISUSB_ID; |
| x.sisusb_version = SISUSB_VERSION; |
| x.sisusb_revision = SISUSB_REVISION; |
| x.sisusb_patchlevel = SISUSB_PATCHLEVEL; |
| x.sisusb_gfxinit = sisusb->gfxinit; |
| x.sisusb_vrambase = SISUSB_PCI_PSEUDO_MEMBASE; |
| x.sisusb_mmiobase = SISUSB_PCI_PSEUDO_MMIOBASE; |
| x.sisusb_iobase = SISUSB_PCI_PSEUDO_IOPORTBASE; |
| x.sisusb_pcibase = SISUSB_PCI_PSEUDO_PCIBASE; |
| x.sisusb_vramsize = sisusb->vramsize; |
| x.sisusb_minor = sisusb->minor; |
| x.sisusb_fbdevactive = 0; |
| x.sisusb_conactive = 0; |
| memset(x.sisusb_reserved, 0, sizeof(x.sisusb_reserved)); |
| |
| if (copy_to_user((void __user *)arg, &x, sizeof(x))) |
| retval = -EFAULT; |
| |
| break; |
| |
| case SISUSB_COMMAND: |
| |
| if (copy_from_user(&y, (void __user *)arg, sizeof(y))) |
| retval = -EFAULT; |
| else |
| retval = sisusb_handle_command(sisusb, &y, arg); |
| |
| break; |
| |
| default: |
| retval = -ENOTTY; |
| break; |
| } |
| |
| err_out: |
| mutex_unlock(&sisusb->lock); |
| return retval; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long sisusb_compat_ioctl(struct file *f, unsigned int cmd, |
| unsigned long arg) |
| { |
| switch (cmd) { |
| case SISUSB_GET_CONFIG_SIZE: |
| case SISUSB_GET_CONFIG: |
| case SISUSB_COMMAND: |
| return sisusb_ioctl(f, cmd, arg); |
| |
| default: |
| return -ENOIOCTLCMD; |
| } |
| } |
| #endif |
| |
| static const struct file_operations usb_sisusb_fops = { |
| .owner = THIS_MODULE, |
| .open = sisusb_open, |
| .release = sisusb_release, |
| .read = sisusb_read, |
| .write = sisusb_write, |
| .llseek = sisusb_lseek, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = sisusb_compat_ioctl, |
| #endif |
| .unlocked_ioctl = sisusb_ioctl |
| }; |
| |
| static struct usb_class_driver usb_sisusb_class = { |
| .name = "sisusbvga%d", |
| .fops = &usb_sisusb_fops, |
| .minor_base = SISUSB_MINOR |
| }; |
| |
| static int sisusb_probe(struct usb_interface *intf, |
| const struct usb_device_id *id) |
| { |
| struct usb_device *dev = interface_to_usbdev(intf); |
| struct sisusb_usb_data *sisusb; |
| int retval = 0, i; |
| static const u8 ep_addresses[] = { |
| SISUSB_EP_GFX_IN | USB_DIR_IN, |
| SISUSB_EP_GFX_OUT | USB_DIR_OUT, |
| SISUSB_EP_GFX_BULK_OUT | USB_DIR_OUT, |
| SISUSB_EP_GFX_LBULK_OUT | USB_DIR_OUT, |
| SISUSB_EP_BRIDGE_IN | USB_DIR_IN, |
| SISUSB_EP_BRIDGE_OUT | USB_DIR_OUT, |
| 0}; |
| |
| /* Are the expected endpoints present? */ |
| if (!usb_check_bulk_endpoints(intf, ep_addresses)) { |
| dev_err(&intf->dev, "Invalid USB2VGA device\n"); |
| return -EINVAL; |
| } |
| |
| dev_info(&dev->dev, "USB2VGA dongle found at address %d\n", |
| dev->devnum); |
| |
| /* Allocate memory for our private */ |
| sisusb = kzalloc(sizeof(*sisusb), GFP_KERNEL); |
| if (!sisusb) |
| return -ENOMEM; |
| |
| kref_init(&sisusb->kref); |
| |
| mutex_init(&(sisusb->lock)); |
| |
| sisusb->sisusb_dev = dev; |
| sisusb->vrambase = SISUSB_PCI_MEMBASE; |
| sisusb->mmiobase = SISUSB_PCI_MMIOBASE; |
| sisusb->mmiosize = SISUSB_PCI_MMIOSIZE; |
| sisusb->ioportbase = SISUSB_PCI_IOPORTBASE; |
| /* Everything else is zero */ |
| |
| /* Register device */ |
| retval = usb_register_dev(intf, &usb_sisusb_class); |
| if (retval) { |
| dev_err(&sisusb->sisusb_dev->dev, |
| "Failed to get a minor for device %d\n", |
| dev->devnum); |
| retval = -ENODEV; |
| goto error_1; |
| } |
| |
| sisusb->minor = intf->minor; |
| |
| /* Allocate buffers */ |
| sisusb->ibufsize = SISUSB_IBUF_SIZE; |
| sisusb->ibuf = kmalloc(SISUSB_IBUF_SIZE, GFP_KERNEL); |
| if (!sisusb->ibuf) { |
| retval = -ENOMEM; |
| goto error_2; |
| } |
| |
| sisusb->numobufs = 0; |
| sisusb->obufsize = SISUSB_OBUF_SIZE; |
| for (i = 0; i < NUMOBUFS; i++) { |
| sisusb->obuf[i] = kmalloc(SISUSB_OBUF_SIZE, GFP_KERNEL); |
| if (!sisusb->obuf[i]) { |
| if (i == 0) { |
| retval = -ENOMEM; |
| goto error_3; |
| } |
| break; |
| } |
| sisusb->numobufs++; |
| } |
| |
| /* Allocate URBs */ |
| sisusb->sisurbin = usb_alloc_urb(0, GFP_KERNEL); |
| if (!sisusb->sisurbin) { |
| retval = -ENOMEM; |
| goto error_3; |
| } |
| sisusb->completein = 1; |
| |
| for (i = 0; i < sisusb->numobufs; i++) { |
| sisusb->sisurbout[i] = usb_alloc_urb(0, GFP_KERNEL); |
| if (!sisusb->sisurbout[i]) { |
| retval = -ENOMEM; |
| goto error_4; |
| } |
| sisusb->urbout_context[i].sisusb = (void *)sisusb; |
| sisusb->urbout_context[i].urbindex = i; |
| sisusb->urbstatus[i] = 0; |
| } |
| |
| dev_info(&sisusb->sisusb_dev->dev, "Allocated %d output buffers\n", |
| sisusb->numobufs); |
| |
| /* Do remaining init stuff */ |
| |
| init_waitqueue_head(&sisusb->wait_q); |
| |
| usb_set_intfdata(intf, sisusb); |
| |
| usb_get_dev(sisusb->sisusb_dev); |
| |
| sisusb->present = 1; |
| |
| if (dev->speed == USB_SPEED_HIGH || dev->speed >= USB_SPEED_SUPER) { |
| int initscreen = 1; |
| if (sisusb_init_gfxdevice(sisusb, initscreen)) |
| dev_err(&sisusb->sisusb_dev->dev, |
| "Failed to early initialize device\n"); |
| |
| } else |
| dev_info(&sisusb->sisusb_dev->dev, |
| "Not attached to USB 2.0 hub, deferring init\n"); |
| |
| sisusb->ready = 1; |
| |
| #ifdef SISUSBENDIANTEST |
| dev_dbg(&sisusb->sisusb_dev->dev, "*** RWTEST ***\n"); |
| sisusb_testreadwrite(sisusb); |
| dev_dbg(&sisusb->sisusb_dev->dev, "*** RWTEST END ***\n"); |
| #endif |
| |
| return 0; |
| |
| error_4: |
| sisusb_free_urbs(sisusb); |
| error_3: |
| sisusb_free_buffers(sisusb); |
| error_2: |
| usb_deregister_dev(intf, &usb_sisusb_class); |
| error_1: |
| kfree(sisusb); |
| return retval; |
| } |
| |
| static void sisusb_disconnect(struct usb_interface *intf) |
| { |
| struct sisusb_usb_data *sisusb; |
| |
| /* This should *not* happen */ |
| sisusb = usb_get_intfdata(intf); |
| if (!sisusb) |
| return; |
| |
| usb_deregister_dev(intf, &usb_sisusb_class); |
| |
| mutex_lock(&sisusb->lock); |
| |
| /* Wait for all URBs to complete and kill them in case (MUST do) */ |
| if (!sisusb_wait_all_out_complete(sisusb)) |
| sisusb_kill_all_busy(sisusb); |
| |
| usb_set_intfdata(intf, NULL); |
| |
| sisusb->present = 0; |
| sisusb->ready = 0; |
| |
| mutex_unlock(&sisusb->lock); |
| |
| /* decrement our usage count */ |
| kref_put(&sisusb->kref, sisusb_delete); |
| } |
| |
| static const struct usb_device_id sisusb_table[] = { |
| { USB_DEVICE(0x0711, 0x0550) }, |
| { USB_DEVICE(0x0711, 0x0900) }, |
| { USB_DEVICE(0x0711, 0x0901) }, |
| { USB_DEVICE(0x0711, 0x0902) }, |
| { USB_DEVICE(0x0711, 0x0903) }, |
| { USB_DEVICE(0x0711, 0x0918) }, |
| { USB_DEVICE(0x0711, 0x0920) }, |
| { USB_DEVICE(0x0711, 0x0950) }, |
| { USB_DEVICE(0x0711, 0x5200) }, |
| { USB_DEVICE(0x182d, 0x021c) }, |
| { USB_DEVICE(0x182d, 0x0269) }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(usb, sisusb_table); |
| |
| static struct usb_driver sisusb_driver = { |
| .name = "sisusb", |
| .probe = sisusb_probe, |
| .disconnect = sisusb_disconnect, |
| .id_table = sisusb_table, |
| }; |
| |
| module_usb_driver(sisusb_driver); |
| |
| MODULE_AUTHOR("Thomas Winischhofer <thomas@winischhofer.net>"); |
| MODULE_DESCRIPTION("sisusbvga - Driver for Net2280/SiS315-based USB2VGA dongles"); |
| MODULE_LICENSE("GPL"); |
| |