From patchwork Fri Nov 20 03:24:48 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Huang Shijie X-Patchwork-Id: 61541 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id nAK3PNYl012319 for ; Fri, 20 Nov 2009 03:25:23 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758252AbZKTDZO (ORCPT ); Thu, 19 Nov 2009 22:25:14 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1758216AbZKTDZN (ORCPT ); Thu, 19 Nov 2009 22:25:13 -0500 Received: from mail-yw0-f202.google.com ([209.85.211.202]:58615 "EHLO mail-yw0-f202.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757536AbZKTDZH (ORCPT ); Thu, 19 Nov 2009 22:25:07 -0500 Received: by mail-yw0-f202.google.com with SMTP id 40so1937121ywh.33 for ; Thu, 19 Nov 2009 19:25:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references; bh=PbbLytzbupDPF/NPLFz8j5/H3X4G0iqMAzvTWGPh2q0=; b=iV1s6hvqbxJUDVSJBNpiPynTXo0I9rgwkEl9Xr7t+ZlbFeF1fY1Ufek4As1haAN6kx 2gWp7tHalt2uuVmTr9Qli4vcKAF9NKZm3jGd8wFMgEeVBeWxrZHC4F9PQCQJ27UkYyjB s6wPFZt+vP8FRh/JwR6YlGIp+L6LZEEirZ0CQ= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=Uvc3HdMXMJp6GPDhaOEDlJBWrQ/3+uMvOT2UIFjMui9/rmHBd4aNtSmA4bGZW2yJTv 6ozjQsqyuQnR1V9Fe1HvJnfPybJJtJOC2lC4+t9beW1slWlLfGVHRanhLK5NcWxxW76F v513MNSERejXIKgnSvXbi60vS8m1ZD4B4agW4= Received: by 10.150.244.4 with SMTP id r4mr1625330ybh.127.1258687513348; Thu, 19 Nov 2009 19:25:13 -0800 (PST) Received: from localhost.localdomain ([211.144.218.162]) by mx.google.com with ESMTPS id 9sm192108ywe.26.2009.11.19.19.25.10 (version=SSLv3 cipher=RC4-MD5); Thu, 19 Nov 2009 19:25:12 -0800 (PST) From: Huang Shijie To: mchehab@redhat.com Cc: linux-media@vger.kernel.org, Huang Shijie Subject: [PATCH 06/11] add the generic file Date: Fri, 20 Nov 2009 11:24:48 +0800 Message-Id: <1258687493-4012-7-git-send-email-shijie8@gmail.com> X-Mailer: git-send-email 1.6.0.6 In-Reply-To: <1258687493-4012-6-git-send-email-shijie8@gmail.com> References: <1258687493-4012-1-git-send-email-shijie8@gmail.com> <1258687493-4012-2-git-send-email-shijie8@gmail.com> <1258687493-4012-3-git-send-email-shijie8@gmail.com> <1258687493-4012-4-git-send-email-shijie8@gmail.com> <1258687493-4012-5-git-send-email-shijie8@gmail.com> <1258687493-4012-6-git-send-email-shijie8@gmail.com> Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org diff --git a/drivers/media/video/tlg2300/pd-main.c b/drivers/media/video/tlg2300/pd-main.c new file mode 100644 index 0000000..f997e2d --- /dev/null +++ b/drivers/media/video/tlg2300/pd-main.c @@ -0,0 +1,546 @@ +/* + * device driver for Telegent tlg2300 based TV cards + * + * Author : + * Kang Yong + * Zhang Xiaobing + * Huang Shijie or + * + * (c) 2009 Telegent Systems + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vendorcmds.h" +#include "pd-common.h" + +#define VENDOR_ID 0x1B24 +#define PRODUCT_ID 0x4001 +static struct usb_device_id id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 1) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +int debug_mode; +module_param(debug_mode, int, 0644); +MODULE_PARM_DESC(debug_mode, "0 = disable, 1 = enable, 2 = verbose"); + +const char *firmware_name = "tlg2300_firmware.bin"; +struct usb_driver poseidon_driver; +LIST_HEAD(pd_device_list); /*should add a lock*/ + +/* + * send set request to USB firmware. + */ +s32 send_set_req(struct poseidon *pd, u8 cmdid, s32 param, s32 *cmd_status) +{ + s32 ret; + s8 data[32] = {}; + u16 lower_16, upper_16; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + mdelay(30); + + if (param == 0) { + upper_16 = lower_16 = 0; + } else { + /* send 32 bit param as two 16 bit param,little endian */ + lower_16 = (unsigned short)(param & 0xffff); + upper_16 = (unsigned short)((param >> 16) & 0xffff); + } + ret = usb_control_msg(pd->udev, + usb_rcvctrlpipe(pd->udev, 0), + REQ_SET_CMD | cmdid, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + lower_16, + upper_16, + &data, + sizeof(*cmd_status), + USB_CTRL_GET_TIMEOUT); + + if (!ret) { + return -ENXIO; + } else { + /* 1st 4 bytes into cmd_status */ + memcpy((char *)cmd_status, &(data[0]), sizeof(*cmd_status)); + } + return 0; +} + +/* + * send get request to Poseidon firmware. + */ +s32 send_get_req(struct poseidon *pd, u8 cmdid, s32 param, + void *buf, s32 *cmd_status, s32 datalen) +{ + s32 ret; + s8 data[128] = {}; + u16 lower_16, upper_16; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + mdelay(30); + if (param == 0) { + upper_16 = lower_16 = 0; + } else { + /*send 32 bit param as two 16 bit param, little endian */ + lower_16 = (unsigned short)(param & 0xffff); + upper_16 = (unsigned short)((param >> 16) & 0xffff); + } + ret = usb_control_msg(pd->udev, + usb_rcvctrlpipe(pd->udev, 0), + REQ_GET_CMD | cmdid, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + lower_16, + upper_16, + &data, + (datalen + sizeof(*cmd_status)), + USB_CTRL_GET_TIMEOUT); + + if (ret < 0) { + return -ENXIO; + } else { + /* 1st 4 bytes into cmd_status, remaining data into cmd_data */ + memcpy((char *)cmd_status, &data[0], sizeof(*cmd_status)); + memcpy((char *)buf, &data[sizeof(*cmd_status)], datalen); + } + return 0; +} + +static int pm_notifier_block(struct notifier_block *nb, + unsigned long event, void *dummy) +{ + struct poseidon *pd = NULL; + struct list_head *node, *next; + + switch (event) { + case PM_POST_HIBERNATION: + list_for_each_safe(node, next, &pd_device_list) { + struct usb_device *udev; + struct usb_interface *iface; + int rc = 0; + + pd = container_of(node, struct poseidon, device_list); + udev = pd->udev; + iface = pd->interface; + + rc = usb_lock_device_for_reset(udev, iface); + if (rc >= 0) { + usb_reset_device(udev); + usb_unlock_device(udev); + } + } + break; + case PM_HIBERNATION_PREPARE: + list_for_each_entry(pd, &pd_device_list, device_list) { + if (pd->interface->pm_usage_cnt <= 0) { + pd->state |= POSEIDON_STATE_IDLE_HIBERANTION; + usb_autopm_get_interface(pd->interface); + } + /* for audio */ + if (pd->audio.users) + pd->audio.pm_state = 1; + } + break; + default: + log("event :%ld\n", event); + } + return 0; +} + +static struct notifier_block pm_notifer = { + .notifier_call = pm_notifier_block, +}; + +int usb_softrst(struct poseidon *pd, s32 udelay) +{ + s32 ret = 0; + s32 cmd_status = 0; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + ret = send_set_req(pd, CMD_CHIP_RST, udelay, &cmd_status); + if (ret || cmd_status) + return -ENXIO; + return ret; +} + +int set_tuner_mode(struct poseidon *pd, unsigned char mode) +{ + s32 ret, cmd_status; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + ret = send_set_req(pd, TUNE_MODE_SELECT, mode, &cmd_status); + if (ret || cmd_status) + return -ENXIO; + return 0; +} + +enum tlg__analog_audio_standard get_audio_std(s32 mode, s32 country_code) +{ + s32 nicam[] = {27, 32, 33, 34, 36, 44, 45, 46, 47, 48, 64, + 65, 86, 351, 352, 353, 354, 358, 372, 852, 972}; + s32 btsc[] = {1, 52, 54, 55, 886}; + s32 eiaj[] = {81}; + s32 i; + + if (mode == TLG_MODE_FM_RADIO) { + if (country_code == 1) + return TLG_TUNE_ASTD_FM_US; + else + return TLG_TUNE_ASTD_FM_EUR; + } else if (mode == TLG_MODE_ANALOG_TV_UNCOMP) { + for (i = 0; i < sizeof(nicam) / sizeof(s32); i++) { + if (country_code == nicam[i]) + return TLG_TUNE_ASTD_NICAM; + } + + for (i = 0; i < sizeof(btsc) / sizeof(s32); i++) { + if (country_code == btsc[i]) + return TLG_TUNE_ASTD_BTSC; + } + + for (i = 0; i < sizeof(eiaj) / sizeof(s32); i++) { + if (country_code == eiaj[i]) + return TLG_TUNE_ASTD_EIAJ; + } + + return TLG_TUNE_ASTD_A2; + } else { + return TLG_TUNE_ASTD_NONE; + } +} + +void poseidon_delete(struct kref *kref) +{ + struct poseidon *pd = container_of(kref, struct poseidon, kref); + + if (!pd) + return; + list_del(&pd->device_list); + + pd_dvb_usb_device_cleanup(pd); + /* clean_audio_data(&pd->audio_data);*/ + + if (pd->udev) { + usb_put_dev(pd->udev); + pd->udev = NULL; + } + if (pd->interface) { + usb_put_intf(pd->interface); + pd->interface = NULL; + } + kfree(pd); + log(); +} + +static int firmware_download(struct usb_device *udev) +{ + int ret = 0, actual_length; + const struct firmware *fw = NULL; + void *fwbuf = NULL; + size_t fwlength = 0, offset; + size_t max_packet_size; + + ret = request_firmware(&fw, firmware_name, &udev->dev); + if (ret) { + log("download err : %d", ret); + return ret; + } + + fwlength = fw->size; + + fwbuf = kzalloc(fwlength, GFP_KERNEL); + if (!fwbuf) { + ret = -ENOMEM; + goto out; + } + memcpy(fwbuf, fw->data, fwlength); + + max_packet_size = udev->ep_out[0x1]->desc.wMaxPacketSize; + log("\t\t download size : %d", (int)max_packet_size); + + for (offset = 0; offset < fwlength; offset += max_packet_size) { + actual_length = 0; + ret = usb_bulk_msg(udev, + usb_sndbulkpipe(udev, 0x01), /* ep 1 */ + fwbuf + offset, + min(max_packet_size, fwlength - offset), + &actual_length, + HZ * 10); + if (ret) + break; + } + kfree(fwbuf); +out: + release_firmware(fw); + return ret; +} + +/* one-to-one map : poseidon{} <----> usb_device{}'s port */ +static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev) +{ + pd->portnum = udev->portnum; +} + +/* fixup something for poseidon */ +static inline struct poseidon *fixup(struct poseidon *pd) +{ + list_del(&pd->device_list); + + kref_get(&pd->kref); + + /* old udev and interface have gone, so put back reference . */ + usb_put_dev(pd->udev); + usb_put_intf(pd->interface); + usb_autopm_set_interface(pd->interface); + + log("event : %d\n", pd->msg.event); + return pd; +} + +static struct poseidon *find_old_poseidon(struct usb_device *udev) +{ + struct poseidon *pd; + + list_for_each_entry(pd, &pd_device_list, device_list) { + if (pd->portnum == udev->portnum && in_hibernation(pd)) + return fixup(pd); + } + return NULL; +} + +#ifdef CONFIG_PM +/* Is the card working now ? */ +static inline int is_working(struct poseidon *pd) +{ + if (pd->state & POSEIDON_STATE_IDLE_HIBERANTION) + return 0; + return pd->interface->pm_usage_cnt > 0; +} + +static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct poseidon *pd = usb_get_intfdata(intf); + + if (!is_working(pd)) { + if (pd->interface->pm_usage_cnt <= 0 + && !in_hibernation(pd)) { + pd->msg.event = PM_EVENT_AUTO_SUSPEND; + pd->pm_resume = NULL; /* a good guard */ + printk(KERN_DEBUG "\n\t ++ TLG2300 auto suspend ++\n"); + } + return 0; + } + pd->msg = msg; + return pd->pm_suspend ? pd->pm_suspend(pd) : 0; +} + +static int poseidon_resume(struct usb_interface *intf) +{ + struct poseidon *pd = usb_get_intfdata(intf); + + printk(KERN_DEBUG "\n\t ++ TLG2300 resume ++\n"); + if (!is_working(pd)) { + if (PM_EVENT_AUTO_SUSPEND == pd->msg.event) + pd->msg = PMSG_ON; + return 0; + } + return pd->pm_resume ? pd->pm_resume(pd) : 0; +} + +static void hibernation_resume(struct work_struct *w) +{ + struct poseidon *pd = container_of(w, struct poseidon, pm_work); + + log(); + pd->msg.event = 0; /* clear it here */ + pd->state &= ~POSEIDON_STATE_DISCONNECT; + + /* + * we hibernated in the order : close() --> disconnect() + * so now ,we must do in reverse order : open() --> resume(); + */ + if (!pd->pm_open(pd->file_for_stream)) + pd->pm_resume(pd); +} +#endif + +static bool check_firmware(struct usb_device *udev, int *down_firmware) +{ + void *buf; + int ret; + struct cmd_firmware_vers_s *cmd_firm; + + buf = kzalloc(sizeof(*cmd_firm) + sizeof(u32), GFP_KERNEL); + if (!buf) + return -ENOMEM; + ret = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + REQ_GET_CMD | GET_FW_ID, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, + 0, + buf, + sizeof(*cmd_firm) + sizeof(u32), + USB_CTRL_GET_TIMEOUT); + kfree(buf); + + if (ret < 0) { + *down_firmware = 1; + return firmware_download(udev); + } + return ret; +} + +static int poseidon_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct poseidon *pd = NULL; + int ret = 0; + + check_firmware(udev, &ret); + if (ret) + return 0; + + pd = find_old_poseidon(udev); + if (!pd) { + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + kref_init(&pd->kref); + set_map_flags(pd, udev); + } + + pd->udev = usb_get_dev(udev); + pd->interface = usb_get_intf(interface); + pd->country_code = 86; + usb_set_intfdata(interface, pd); + mutex_init(&pd->lock); + + /* register devices in /dev */ + pd_video_init(pd); + poseidon_audio_init(pd); + vbi_init(pd); + + poseidon_fm_init(pd); + pd_dvb_usb_device_init(pd); + + INIT_LIST_HEAD(&pd->device_list); + list_add_tail(&pd->device_list, &pd_device_list); + + device_init_wakeup(&udev->dev, 1); +#ifdef CONFIG_PM + pd->udev->autosuspend_disabled = 0; + pd->udev->autoresume_disabled = 0; + pd->udev->autosuspend_delay = HZ * PM_SUSPEND_DELAY; + + if (in_hibernation(pd)) { + INIT_WORK(&pd->pm_work, hibernation_resume); + schedule_work(&pd->pm_work); + } +#endif + return 0; +} + +static void poseidon_disconnect(struct usb_interface *interface) +{ + struct poseidon *pd = usb_get_intfdata(interface); + + if (!pd) + return; + + mutex_lock(&pd->lock); + pd->state |= POSEIDON_STATE_DISCONNECT; + mutex_unlock(&pd->lock); + + /* stop urb transferring */ + pd_video_stop_stream(pd); + dvb_stop_streaming(&pd->dvb_data); + + lock_kernel(); + { + pd_dvb_usb_device_exit(pd); + poseidon_fm_exit(pd); + + vbi_exit(pd); + poseidon_audio_free(pd); + pd_video_exit(pd); + } + unlock_kernel(); + + usb_set_intfdata(interface, NULL); + kref_put(&pd->kref, poseidon_delete); +} + +struct usb_driver poseidon_driver = { + .name = "poseidon", + .probe = poseidon_probe, + .disconnect = poseidon_disconnect, + .id_table = id_table, +#ifdef CONFIG_PM + .suspend = poseidon_suspend, + .resume = poseidon_resume, +#endif + .supports_autosuspend = 1, +}; + +static int __init poseidon_init(void) +{ + int ret; + + ret = usb_register(&poseidon_driver); + if (ret) + return ret; + register_pm_notifier(&pm_notifer); + return ret; +} + +static void __exit poseidon_exit(void) +{ + unregister_pm_notifier(&pm_notifer); + usb_deregister(&poseidon_driver); +} + +module_init(poseidon_init); +module_exit(poseidon_exit); + +MODULE_AUTHOR("Telegent Systems"); +MODULE_DESCRIPTION("For tlg2300-based USB device "); +MODULE_LICENSE("GPL");