text:
Canada, Environment, Food, Health, PPGR Abroad, Public Policy
Food Labelling: The Right to Know if You're Eating GMOs
Posted by Margaret Campbell on November 7, 2014 November 19, 2014
The 'Public Policy and Governance Review Abroad', or PPGR Abroad, is a new initiative for 2014. Undertaken in collaborative with exchange students from the Master of Public Policy program at the School of Public Policy and Governance, University of Toronto, it will featured policy insights and analyses direct from Berlin and Paris.
Margaret Campbell
After a year of public campaigning, teen activist Rachel Parent will finally get her meeting with Minister of Health Rona Ambrose to discuss the labelling of foods that contain Genetically Modified Organisms (GMOs) in Canada. News that Ambrose will meet with Parent— who started the non-profit Kids Right to Know— comes only weeks after research released by Consumer Reports found that a high percentage of North American food products labelled 'natural' actually contain GMOs.
South of the border, recent midterm elections in the United States saw residents of both Colorado and Oregon vote on whether packaged foods containing genetically engineered ingredients should be labelled as such. Both measures were defeated, although narrowly — fewer than 51 per cent of voters cast their ballot against labelling in Oregon. Back in May, Vermont enacted the country's first law requiring the labelling of food containing GMOs. That law is scheduled to come into effect in 2016.
GMOs are organisms that have had their genes altered to act in an unnatural way, or that contain genes from another organism. In Canada, about 70 per cent of the processed food found in grocery stores includes some genetically engineered ingredient — yet there are no laws in the country requiring that they be labelled to reflect this.
The lack of laws or legislation governing GMOs in Canada has been increasingly criticized by those who link the continued growth and consumption of generically modified crops to skyrocketing allergy rates, immune reactions, and liver problems. Public opinion polls conducted over the past decade have found that between 80 and 90 per cent of Canadians support the labelling of GMO foods.
NDP Member of Parliament Murray Rankin has recently introduced a motion into the House of Commons calling for the mandatory labelling of all food products containing genetically engineered ingredients. This motion has been endorsed by a wide range of supporters, including Whole Foods, the National Farmer's Union, and the Canadian Biotechnology Action Network (CBAN). But it remains caught up in the bureaucratic process. When it comes to regulating GMOs, Canada has fallen drastically behind the times.
Internationally, 64 countries already require that genetically modified foods be either labelled, restricted, or banned entirely – including China and South Africa. The European Union has been a world leader in this area, requiring the labelling of all such products since 1997. Regulations are so stringent in the EU that GMO labels are required even if the product is not packaged or does not have detectable traces of GMO in the final product.
There is, however, some disagreement on how stringent GMO regulation should be across Europe. In 2008, the European Union's highest court fined France €10 million for its delay in updating domestic laws to line up with established directives. Yet, just like in Canada, public opinion in France tends to oppose the production and consumption of genetically modified foods; a 2011 poll found that 80 per cent of respondents were opposed to the cultivation of GMO crops in open fields. In May of this year, the French parliament approved a law prohibiting the cultivation of any type of genetically engineered maize. National bans on genetically modified crops also exist in Austria, Greece, Hungary, and Italy.
In contrast, Health Canada and the Canadian Food Inspection Agency – which share responsibility for developing the policy on labelling GMO foods in Canada – have approved over 81 genetically modified foods since 1991. Canada is also one of five countries which combine to produce 90 per cent of the world's genetically engineered crops.
Minister Ambrose has publicly maintained that there is no evidence that genetically modified foods are unhealthy. Monsanto, a multinational agricultural biotechnology company, has affirmed that it has never tested GMO safety on humans given that they are 'substantively equivalent' to natural organisms. On the other hand, Physicians and Scientists for Responsible Application of Science and Technology have argued that engineered crops lead to inflammation and many related health issues.
Despite releasing its own report in June 2014 acknowledging that Canadians want the federal government to act on GMO labelling, Health Canada has no plans to conduct any long-term studies on the health impact of genetic engineering unless "products which represent true novelty to the food supply [are] proposed for commercialization."
Canada's federal government has no apparent interest in enacting legislation to govern GMO labelling. But labelling is not just a safety issue. Food labels provide information on everything from allergy risks to the presence of substances people choose to limit, and Canadians who choose to limit their GMO consumption should have that information made easily accessible to them – as is now the case in many developed countries. Proper labelling of food products will be particularly relevant if the government continues to show little interest in conducting further scientific research into the potential health consequences of genetically modified foods.
Margaret Campbell is in her second year at the School of Public Policy and Governance and is spending her fall semester studying at the Paris School of International Affairs. Her policy interests include education, gender and family policy, and criminal justice reform. Having just finished a summer co-op placement in education labour relations within the Ontario Public Service, Margaret is interested in gaining a better understanding of the policies in France that promote a healthy work-life-family balance. She is also looking forward to eating macarons while wandering her new neighbourhood of Montmartre.
European UnionFood PolicyGenetically Modified FoodsGenetically Modified OrganismsGMOHealthHealth CanadaPublic OpinionRegulation
Previous Post Milking Canadians: The Consumer Costs of Supply Management in the Dairy Industry
Next Post STEM-ing the Achievement Gap: Looking Beyond the Classroom
GinoBrayberjt says:
I noticed your site's ranking in google's search results is very low.
You are loosing a lot of traffic. You need hi PageRank backlinks
to rank in top ten. I know – buying them is too expensive.
It's better to own them. I know how to do that, just google it:
Polswor's Backlinks Source


text:
Group Visits & Tours
Public Programs and Events
Digital Dorsky
New Paltz Faculty & Students
Selections from the Collection
Arts News & Events
Membership and Giving
Friends of the Dorsky
Proposals for Art Donations
In 1985 an anonymous bequest of a collection of historical and modern silver helped to establish a new area of focus for the permanent collection at the museum. From the outset, collecting "metals" was seen as a way to both complement and augment the University's internationally renowned studio program in metalsmithing. Through the collaborative efforts and generosity of former and current faculty members, alumni, and friends of the museum, the collection is being aggressively developed. Over the past two years the collection has undergone a period of unprecedented growth and now includes fine examples of 18th and19th century silver, as well as outstanding examples of modern and contemporary jewelry, vessels, and small sculpture.
Sergey Jivetin, Cupola #4, 2005, stainless steel, 2005.002
Unknown Norwegian artisan, pin, c. 1955-1960 sterling silver, enamel, 2000.008.004
Mary Preston, Chromosome (brooch), 2002, silver, gold, pearl, 2005.019
Eric Martin, Two Goblets, 1973, silver, horn, bone, pearl, bronze, 1996.013.009a-b
© Samuel Dorsky Museum of Art
SUNY New Paltz 1 Hawk Drive, New Paltz, NY 12561
For more information contact us at sdma@newpaltz.edu

redpajama_set_name: RedPajamaCommonCrawl


Since its conception, Hub Group Dedicated has seen immense growth in our industry based on the simple fact that we give our partners what they want and how they want it. This is the core of our dedicated service culture and promoted by our outstanding leadership team.
The most distinct advantage we have over our competitors is that we hire and train our employees with an entrepreneurial mindset coupled with a minimum level of bureaucracy associated with the way we run our operations. Our site managers are trained empowered to make decisions at their location when they need to and do not have to wait for approval from corporate to perform their duties. Each site operates as if it were our only location and we make sure that our employees are free to work without any hindrance or red tape.
While working with our partners, we continue to innovate and excel by providing continuous improvement initiatives. Once we get into a location, we constantly attempt to find improvements and efficiencies that will translate into continued cost saving efforts on both sides. This in-depth level of service builds confidence with our partners and allows us to seek relationships with new partners that will help both parties receive the same great service we are known for.


text:
Elena Chabo
Priyanka Chopra Jonas is known for her long, flowing dark tresses, but just debuted a new short and choppy style we'd know anywhere. The iconic 'The Rachel' haircut, named after Rachel Green from Friends, has been popping up over the last few years (to be expected with all the 90s and 00s comebacks). But as shaggy wolf cuts and choppy bobs began to dominate in late 2022, with no signs of slowing down in 2023, expect to see a lot more of it.
Priyanka's new short 'do shows off her natural hair length with no extensions. Hairstylist Bridget Brager framed the face with the same kind of bouncy shoulder-skimming layers that had every girl grasping pages of the Jennifer Aniston filled weeklies on their way to the hairdressers during peak Friends mania. Of course, the look wouldn't be complete without 00s streaky honey blonde highlights, too.
It's not the first peek we got at short-haired Priyanka. The new mum posted a couple of days before with her hair worn in its natural waves, giving the length a choppy, tousled bob style. The low-maintenance length is giving 'cool mom' in the family snap with husband Nick Jonas and their baby Malti Marie, who was born via surrogate last summer.
The Rachel haircut is a glam switch up for the short length, styled for an afternoon with Bulgari, and naturally needed makeup glam to match. Mary Phillips, the creator of the 'backwards makeup' trend, did the honours. Mary painted Priyanka's face with a soft nude eye, long wispy lashes, and a delicious amount of pink blush. Finishing with a lip colour I'm convinced is a nod to Rachel Green's iconic MAC Paramount hue of a rust-raisin neutral.
BRB off to rewatch friends for the 176383th time.
Follow Elena on Instagram
A ranking of the very best hair straighteners - according to our Beauty Editors
Best party dresses to shop in the UK right now
11 products you'd be mad to miss from the Net A Porter beauty sale
Gemma Owen wows in pink corset bridesmaid dress - see photo
Gemma Owen has undeniably been quite the style icon since her departure from the 2022 series of Love Island. With everyone going crazy over her infamous Abbot Lyon tennis necklace (that is still in stock by the way!), it comes as no surprise that her clothing choices have had us equally captivated. In the most recent promo picture for the 19-year-old's drool-worthy Abbot Lyon jewellery collection, she has shared a photograph of herself on Instagram wearing the stunning House of CB 'Loretta' dres
Monkey attacks tourist after stealing her bag
A cheeky macaque stole a tourist's bag on a beach in the Thai province of Krabi on January 24. But when the owner tried to recover the stolen bag, the monkey turned savage.
Ally of former Pakistani PM Imran Khan jailed ahead of trial
A Pakistani court on Friday sent to jail for two weeks a senior ally of former Prime Minister Imran Khan, after his arrest over allegations that he threatened the chief of the elections oversight body and other government officials. Khan was ousted by a parliamentary no-confidence vote in April, but has refused to accept that outcome and has persisted in his calls for early elections, leading to a political standoff with the current government of Prime Minister Shahbaz Sharif. Khan's supporter Fawad Chaudhry was arrested from the eastern city of Lahore on Wednesday following a complaint from the Election Commission of Pakistan, which has accused him of threatening its head, Sikandar Sultan Raja, and other officials.
Priyanka Chopra and Nick Jonas have matching tattoos inspired by his proposal
Priyanka Chopra and her husband Nick Jonas have matching tattoos as a tribute to his proposal. In a Pieces of Me video interview with British Vogue, the 40-year-old actress revealed the sentimental significance behind their tattoos. "I have a check and a box behind my ears. My husband has them on his arms because when he proposed, he asked me, that I checked all his boxes, and would I check another one?" she recalled. Priyanka did not reveal her hidden tattoos, but did express her admiration for
Pamela Anderson knew things wouldn't work with Kid Rock
Pamela Anderson knew "right when [she] got married" that things wouldn't work out with Kid Rock but she went through with the ceremony anyway because she was still in "pain" over the end of her relationship with Tommy Lee.
Kim Kardashian's go-to self-tanner for travel is on sale for just $13 at Amazon
"It's the best tan in a can!" shared one of over 3,000 five-star Amazon reviewers.
The best bra ever really does exist, and it's currently on sale at Nordstrom for 40% off
Ruby Stokes on Lockwood & Co's Lucy Carlyle and leaving Bridgerton behind
Ruby Stokes from Netflix's Lockwood & Co opens up about everything from playing Lucy Carlyle and her time at the Brit School, to leaving Bridgerton behind.
Hollywood has become 'completely dysfunctional': Shyamalan
From "The Sixth Sense" to "Old", director M. Night Shyamalan has had a unique string of hits, but these days he works outside the Hollywood studio system, which he says has grown "sick".Still just 52, he has been cranking out box office hits every couple of years since the iconic "The Sixth Sense" in 1999, through "Unbreakable", "Signs", "Split" and many more.
Everything Everywhere All at Once leads 2023 Oscar nominations
Everything Everywhere All at Once leads the nominees for the 2023 Academy Awards with 11. The multiverse-hopping action-comedy scooped nominations for Best Picture, Best Actress for Michelle Yeoh, Best Supporting Actor for Ke Huy Quan, Best Supporting Actress for Jamie Lee Curtis and Stephanie Hsu and Best Director and Best Original Screenplay for Daniel Kwan and Daniel Scheinert, among others. The other frontrunners were Martin McDonagh's The Banshees of Inisherin and the German war epic All Qu
Ant-Man and The Wasp: Quantumania Premiere moved up
The Premiere for Ant-Man and The Wasp: Quantumania has been moved closer than originally planned.
Before they were royal: the jobs they had before they married a Prince or Princess
Some of the world's famous faces started out just like us, before they met the royal loves of their lives and married into monarchies, that is! We've taken a trip down memory lane to see what the Princess of Wales, the Duchess of Sussex, the Queen Consort Camilla and more got up to before they found their Prince or Princess and had their fairytale weddings. WATCH: Princess Kate says the sweetest thing about Prince William The Princess of Wales Kate, who married Prince William in 2011, is known f
Shotgun Wedding: Jennifer Coolidge as a mother-in-law with a machine gun? Well, it can't be all bad...
Shotgun Wedding describes itself twice. As written, it's the story of Jennifer Lopez's big day if it were hijacked on an island paradise by armed pirates – see what they did there. But the production was also a gunpoint quest for a replacement groom, when a scandal-beset Armie Hammer quit the shoot just before it started, leaving J.Lo desperate at the altar.
Why lunges are one of the best strength moves a runner can do
Loading your quads and glutes, lunges are a powerhouse move for runners. Here's how to do them correctly, including forward lunges and reverse lunges, and their benefits.
Akon shocks fans by ranting men are 'divine kings' to be worshipped by women
Akon has launched into a shock gender roles rant by saying men are "divine kings" and "gods" who deserve to be worshipped and served by women.


We start the video turning onto the Sam Rayburn Tollway at its eastern terminus at Texas Highway 5 (McDonald St) in McKinney. Continuing southwest, we soon cross the stack interchange with U.S. Highway 75 before continuing on towards the Dallas North Tollway and Frisco.
We end the video as we take the exit for Dallas Pkwy, just before the DNT, in Frisco.


Despite a strong start with a win in the opening set, the Tigers couldn't close out the undefeated Titans.
Girls' volleyball lost to San Marino High School in four sets on Wednesday, October 10 in their final game before the postseason. The Tigers final league record stands at 7-3.
The Tigers started off the first set behind the Titans. San Marino played with full force, but South Pas was able to catch up. Sophomore Lexi Schroeder set the ball to sophomore Lola Foord, who spiked the ball, sending San Marino scrambling to retrieve the ball. It was uphill from there, with an ace by senior Lindsey Kuwahara tying the score at 16. The last few points of the set were close, with both teams staying neck and neck until the final point. With a serve to the net by San Marino, the Tigers won the set, 25-23.
SPHS began the second set with an ace by Schroeder, establishing an early lead for the Tigers. From there, senior Christina Taylor and sophomore Lola Foord blocked a powerful hit from San Marino. However, San Marino ultimately won the set, 25-18.
South Pas fell quickly behind in the third set. Although the Tigers continued their solid play, including a spike by freshman Ally Sokolow, SPHS couldn't get around San Marino's skilled setter. Nonetheless, the Tigers communicated well with each other and were able to score several crucial points. However, South Pas lost the set, 25-14.
The team celebrated Senior Night in their last home game.
Determined to win the fourth and final set, South Pas played hard until the very end. The set remained close, with both teams scoring one after each other. Foord hit powerful spikes to the other side, however, several were blocked by the Titans. The game came down to the wire toward the end, but due to South Pas' mishandlings, San Marino won the set, 25-22.
After the game, the Tigers bid farewell to the seniors on the team with speeches, gifts, and a slideshow. However, the girls' volleyball season isn't over, as they will go on to play in CIF after a second place finish in league.


When you open a new RDP session from a Windows machine sometimes the keyboard does not work correctly.
Once the RDP session has started and you have logged in successfully, when you press the T button it switches between taskbar windows rather than simply typing the letter T. It looks like it performs the same function as ALT + T although ALT is not pressed.
Within the RDP session presss the ALT button several times.


I'm in Simi Valley, spend as much weekend time as possible in Ventura, and would be up for a meetup when the weather gets a bit better.
I'm in the west side of the san fernando valley and would love to join you guys during the next meet up.
Adding to follow. I'm in Culver City. If there's a meet up I would love to try to make it.
Same here. I'm in Northridge/North San Fernando Valley. Hoping we do something soon so I can finally meet another mustachian.
Hey Money and Myhotrs, either of you up for a meetup on Saturday, January 30? I'm thinking Balboa Park since it's centrally located in the SFV and right by the bike path. I'm in NoHo. Let me know(Ho).
I'm in, lets do it. If anyone is into hiking I know some awesome nature hikes in the north valley. Everything is turning green after all the rain making for some very pretty hikes.
I'm not a huge fan of LA, but we do have a ton of great trails.
Eh, this is the 2nd time this has happened to me on this board. I need to cancel for Jan 30. I just found out that a family gathering has been planned for that same day, and my family meets up like once a year, so it's not something I should miss.
Are you down for Saturday, February 6 instead? A hike sounds good to me, as well. Let me know, and sorry about the cancellation!
Anyone in here from the North side of LA county??
Hey all, just moved to Sherman Oaks from IL. It seems like we have quite a bit of interest, so let's start planning something! Later this month, maybe?
Cool, I'm just a few miles away. Dad lives in Sherman Oaks brother in Encino, really nice areas. Welcome!
Is anyone down to meet up on Saturday, February 27, somewhere in the San Fernando Valley? A hike is probably not a bad idea, but we can decide on the specifics later. Let's first see if anyone is interested. Thanks!
New mustachian checking in! Way up north in the desert of Ridgecrest but LA (maybe Bakersfield) are really the closest civilized areas to me! Would love to meet like minded people sometime!
Hello and welcome! What you do up there in them hills anyways?
Following up on this again. Anyone interested in meeting up? Seems like we have a few people in the SFV now. March 19? Any takers?
I'm in the Navy. Temporarily station in the middle of no water!
Hey everyone. I'm getting ready to make the move back to LA from Seattle in the next few months (grew up in Pasadena but have been in Seattle since 2000). Looking forward to future meet-ups!
My hubby and I are located in Long Beach and wanted to invite the OC and South Bay mustachians out to Seal beach to meet at the southern edge of Electric Ave Median Park. There is an arbor and some benches.
I'll cross post in the OC board as well.
I've posted this info on the OC thread and it looks like we'll have a few people there.
We'll be at the Manhattan Beach meetup on April 8th. Haven't been to a MMM gathering before, very excited!
Embarrassingly, its a bit of a drive for us since we live in OC. Doing it as a treat for ourselves, one of those lifetime experiences type of thing, since the man himself will be there.
Considering my GF is the one who introduced me to MMM, she'll be quite upset that she is stealing me away for a wedding that weekend!
Very exciting! Can't wait to meet you all there. If anyone wants to carpool from the LB area, let me know!
I'll be there! I live in LB but work in El Segundo so it's too easy to swing by and say hello. Looking forward to meeting everyone.
I plan to be there. Friday weather says "a brief shower or two". That may turn into no rain or more rain.
MMM- Suggest there's an alternate meeting spot in event of inclement weather.
Anyone want to meet in Sherman oaks and carpool to meet the man?
Totally coming to Manhattan Beach! I'm working in Century City until 6:30 and then carpooling over, someone please post if location moves due to weather. So excited, you guys!!
I can't help but think MMM will laugh at our consternation about the weather. We really are spoiled here when overcast skies and the threat of light rain dissuades us.
I'm also sadistically hoping MMM gets of a taste of LA traffic in the rain.
If there is a change in the time or location of the meet, hopefully someone will post it. A sunny beach bike ride now looks like a sluggish car clown traffic jam - at least from where we're coming.
Should we just pick someplace with a good happy hour? Any suggestions? I've only ever been to Simmzy's, but there's no way they could handle a group on a Friday night.
Good meetup in Manhattan beach. Forgot to get people's contact info.
Indeed. Was good meeting everyone. Let's have another meetup soon.
Another Culver City resident checking in. Went to the meetup in MB last night, let's get something set up in Culver City or West LA, I saw a number of people in this area post earlier on this thread.
Hi all - I live in Mar Vista and work in Culver City and would be interested in attending a meetup on the Westside.
What's your definition of north? Antelope Valley?
Great meeting some of you at the MMM meetup in MB. I'd be up for another on the Westside or in northerly LA. I live and work most days in Simi Valley.
Just a reminder to anyone near the South Bay, there is a mustache meetup on Sat April 23rd at Seal Beach at the South end of park on Electric Ave Median Park at 10:30am. Link to Facebook event.
Anyone feel like going hiking in the Canyon or something?
I live in Van Nuys but work in Northridge, not far at all from you! PM'd.
If anyone else wants to come down to the San Fernando Valley, I will gladly coordinate something! If we get enough people, I may even be able to rent out a gathering room at Cal State University, Northridge.
I'm in Northridge and will be unemployed soon :) so will have lots of availability. To keep myself from the depression everyone tells me is coming after I retire I would love to hike more with like-minded folks!
What's up Myhotrs. I'm 1 year your elder and live a few miles south of you in Tarzana. Actually just moved from Tarzana. You've beat me to unemployment by a few years, I'm afraid, but I'm still down to hang out. I don't hold a grudge! What day and time are you thinking of?
I'm free next Sunday, the 26th.
There are loads of great hiking meetups in and around the valley.
MeetUp is a great resource! I am more here to meet with like-minded people in regards to finance / investing...etc.
I am only 25, but willing to hang with anyone!
I'd be down for a spontaneous meetup on Sunday. However I will be out hiking from 8-2 or so.
Awesome - hopefully more will join, if so we can all meet at a park or something.
If not, we can just meet at a local Starbucks.
LET'S GO LA MMMer's, I KNOW YOU ARE HERE!
Myhotrs, let me know where you want to hike this Sunday (26th) and what time.
I'm following to join up for a non-hiking outing (I can't walk more than about 5 minutes at a time, so I'd be a sad hiking buddy. :p).
I'm open all day after 10am, I live in Northridge and there are a lot of great hikes in the area. So if anyone is ever in the Valley, post here or PM me and we can meet up.
Anyone still active on this thread? We live in Long Beach (work in El Segundo). Looking to meet up. Feel free to reach out!


text:
C O N C E R T S
2020/2021 CONCERT SERIES
VIRTUAL CONCERT HALL
MAHLER PROJECT
PAST CONCERT HIGHLIGHTS
S U P P O R T
C O N T A C T / L O C A T I O N
Here are some memorable musical moments from our recent concerts:
Philip Chiu, Piano
Piano Sonata in C sharp minor, op. 27 no. 2 "Moonlight"
i. Adagio Sostenuto
ii. Allegretto
iii. Finale: presto agitato
Claude Debussy (1862-1918)
Suite Bergamasque
i. Prélude
ii. Menuet
iii. Clair de lune
iv. Passepied
"A pianist-painter who transforms each musical idea into a beautiful array of colors" (La Presse), Philip Chiu is acclaimed for his brilliant pianism, sensitive listening, and a stage presence that eschews the hermit-pianist image and favours openness, authenticity, and connection with audiences. Inaugural winner of the Mécénat Musica Prix Goyer ($125,000), Philip has become one of Canada's leading musicians through his infectious love of music and his passion for creation and communication.Philip concertizes extensively as soloist and chamber musician and has performed solo recitals and chamber music concerts in most major venues across Canada, as well as concert halls in France, Japan and the United States. Chamber music partners have included James Ehnes, Emmanuel Pahud, Regis Pasquier, Noah Bendix-Balgley, Bomsori Kim, Johannes Moser, and Raphael Wallfisch. He has a long-standing violin-piano duo with Jonathan Crow, concertmaster of the Toronto Symphony Orchestra and violinist of the New Orford String Quartet. Philip is a veteran touring artist of Prairie Debut, Jeunesses Musicales Canada, and Debut Atlantic, having toured the country 14 times with their generous support.He immensely enjoyed programming unique and imaginative concerts as Cecilia Concert's 2018/2019Artist-in-Residence, and is looking forward to exploring new ideas as Artist-in-Residence for Montreal's La Chapelle Historique du Bon-Pasteur in 2020. He will also be debuting with Kitchener-Waterloo Symphony and Vancouver Island Symphony in 2019-2020. Other upcoming projects include a recording/concert tour of John Burge's 24 Preludes for Solo Piano (Centrediscs), as well as a recording/concert project with Pentaèdre, honouring the music of Jacques Hétu (ATMA Classique).Philip has recorded for Warner Music, Analekta, ATMA Classique, and CBC Music. He can be heard on BBC Radio 3, France Musique, ICI Musique, and CBC Music. He is eternally grateful for the support of Mécénat Musica, the Sylva Gelber Music Foundation, and the Canada Council for the Arts. Philip is represented by Andrew Kwan Artists Management.
meagan&amy
Meagan Milatz, piano
Amy Hillis, violin
12th and 13th of December, 2020
The vibrant Canadian duo meagan&amy are the winners of the first-ever "Pan-Canadian Partnership" tour presented by Jeunesses Musicales Canada, Debut Atlantic and Prairie Debut. As a result, the duo embarked on a 2019/2020 tour to 50 different Canadian cities in ten provinces. Featured in a national broadcast on CBC's In Concert in May 2020, their playing has been described as "both fearless and nuanced" (Toronto Concert Reviews). Their debut album, Roots, was released in September 2019 with the generous support of the Canada Council for the Arts.
READ MORE ABOUT THIS CONCERT AND SEE THE PROGRAM
The British Cello
Paul Marleyn, cello
Carson Becke, piano
21st and 22nd of November, 2020
British cellist Paul Marleyn (professor of cello, Ottawa University) and pianist Carson Becke share a musical heritage: both spent a portion of their formative musical years in the United Kingdom, and both were students at the Royal Academy of Music in London. In celebration of this shared heritage, this program explored music for cello and piano by three of the most influential British composers of the twentieth century: Ralph Vaughan-Williams, Frank Bridge, and Benjamin Britten.
Duo Octavian
Suren Barry, piano
23rd and 25th of October, 2020
Carson Becke and Suren Barry, both born and raised in Ottawa, form the two halves of Duo Octavian. The duo is committed to expanding the two-piano repertoire with their own arrangements of various works and with arrangements/commissions by other performers/composers. Recently, Duo Octavian performed their transcription of Johannes Brahms' Ein deutsches Requiem (A German Requiem) with the Ewashko Singers under the direction of Laurence Ewashko. The duo has also presented the Canadian premieres of two arrangements by Russian pianist Dmitri Alexeev, Porgy and Bess Concert Suite No.1 (based on George Gershwin's opera Porgy and Bess) and Stravinsky's The Firebird.
Philip Chiu in Recital
26th and 27th of September, 2020
"A pianist-painter who transforms each musical idea into a beautiful array of colors" (La Presse), Philip Chiu is acclaimed for his brilliant pianism, sensitive listening, and a stage presence that eschews the hermit-pianist image and favours openness, authenticity, and connection with audiences. Inaugural winner of the Mécénat Musica Prix Goyer ($125,000), Philip has become one of Canada's leading musicians through his infectious love of music and his passion for creation and communication.
JOIN OUR E-MAILING LIST
Our e-mailing list is our primary means of communication with our audience. Join our list to be kept up to date about the series, and find out what concerts are coming up next.
© Pontiac Enchanté, all rights reserved.
Crafted lovingly with PageCloud
440 Chemin Crégheur


text:
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#include "usb-serial.h"
#include "keyspan.h"

static int debug;

/*
 * Version Information
 */
#define DRIVER_VERSION "v1.1.4"
#define DRIVER_AUTHOR "Hugh Blemings <hugh@misc.nu"
#define DRIVER_DESC "Keyspan USB to Serial Converter Driver"

#define INSTAT_BUFLEN	32
#define GLOCONT_BUFLEN	64

	/* Per device and per port private data */
struct keyspan_serial_private {
	const struct keyspan_device_details	*device_details;

	struct urb	*instat_urb;
	char		instat_buf[INSTAT_BUFLEN];

	/* XXX this one probably will need a lock */
	struct urb	*glocont_urb;
	char		glocont_buf[GLOCONT_BUFLEN];
};

struct keyspan_port_private {
	/* Keep track of which input & output endpoints to use */
	int		in_flip;
	int		out_flip;

	/* Keep duplicate of device details in each port
	   structure as well - simplifies some of the
	   callback functions etc. */
	const struct keyspan_device_details	*device_details;

	/* Input endpoints and buffer for this port */
	struct urb	*in_urbs[2];
	char		in_buffer[2][64];
	/* Output endpoints and buffer for this port */
	struct urb	*out_urbs[2];
	char		out_buffer[2][64];

	/* Input ack endpoint */
	struct urb	*inack_urb;
	char		inack_buffer[1];

	/* Output control endpoint */
	struct urb	*outcont_urb;
	char		outcont_buffer[64];

	/* Settings for the port */
	int		baud;
	int		old_baud;
	unsigned int	cflag;
	unsigned int	old_cflag;
	enum		{flow_none, flow_cts, flow_xon} flow_control;
	int		rts_state;	/* Handshaking pins (outputs) */
	int		dtr_state;
	int		cts_state;	/* Handshaking pins (inputs) */
	int		dsr_state;
	int		dcd_state;
	int		ri_state;
	int		break_on;

	unsigned long	tx_start_time[2];
	int		resend_cont;	/* need to resend control packet */
};

/* Include Keyspan message headers.  All current Keyspan Adapters
   make use of one of four message formats which are referred
   to as USA-26, USA-28 and USA-49, USA-90 by Keyspan and within this driver. */
#include "keyspan_usa26msg.h"
#include "keyspan_usa28msg.h"
#include "keyspan_usa49msg.h"
#include "keyspan_usa90msg.h"

/* Functions used by new usb-serial code. */
static int __init keyspan_init (void)
{
	int retval;
	retval = usb_serial_register(&keyspan_pre_device);
	if (retval)
		goto failed_pre_device_register;
	retval = usb_serial_register(&keyspan_1port_device);
	if (retval)
		goto failed_1port_device_register;
	retval = usb_serial_register(&keyspan_2port_device);
	if (retval)
		goto failed_2port_device_register;
	retval = usb_serial_register(&keyspan_4port_device);
	if (retval)
		goto failed_4port_device_register;
	retval = usb_register(&keyspan_driver);
	if (retval)
		goto failed_usb_register;

	info(DRIVER_VERSION ":" DRIVER_DESC);

	return 0;
failed_usb_register:
	usb_serial_deregister(&keyspan_4port_device);
failed_4port_device_register:
	usb_serial_deregister(&keyspan_2port_device);
failed_2port_device_register:
	usb_serial_deregister(&keyspan_1port_device);
failed_1port_device_register:
	usb_serial_deregister(&keyspan_pre_device);
failed_pre_device_register:
	return retval;
}

static void __exit keyspan_exit (void)
{
	usb_deregister (&keyspan_driver);
	usb_serial_deregister (&keyspan_pre_device);
	usb_serial_deregister (&keyspan_1port_device);
	usb_serial_deregister (&keyspan_2port_device);
	usb_serial_deregister (&keyspan_4port_device);
}

module_init(keyspan_init);
module_exit(keyspan_exit);

static void keyspan_rx_throttle (struct usb_serial_port *port)
{
	dbg("%s - port %d", __FUNCTION__, port->number);
}

static void keyspan_rx_unthrottle (struct usb_serial_port *port)
{
	dbg("%s - port %d", __FUNCTION__, port->number);
}

static void keyspan_break_ctl (struct usb_serial_port *port, int break_state)
{
	struct keyspan_port_private 	*p_priv;

 	dbg("%s", __FUNCTION__);

	p_priv = usb_get_serial_port_data(port);

	if (break_state == -1)
		p_priv->break_on = 1;
	else
		p_priv->break_on = 0;

	keyspan_send_setup(port, 0);
}

static void keyspan_set_termios (struct usb_serial_port *port,
				     struct termios *old_termios)
{
	int				baud_rate, device_port;
	struct keyspan_port_private 	*p_priv;
	const struct keyspan_device_details	*d_details;
	unsigned int 			cflag;

	dbg("%s", __FUNCTION__);

	p_priv = usb_get_serial_port_data(port);
	d_details = p_priv->device_details;
	cflag = port->tty->termios->c_cflag;
	device_port = port->number - port->serial->minor;

	/* Baud rate calculation takes baud rate as an integer
	   so other rates can be generated if desired. */
	baud_rate = tty_get_baud_rate(port->tty);
	/* If no match or invalid, don't change */
	if (baud_rate >= 0
	    && d_details->calculate_baud_rate(baud_rate, d_details->baudclk,
				NULL, NULL, NULL, device_port) == KEYSPAN_BAUD_RATE_OK) {
		/* FIXME - more to do here to ensure rate changes cleanly */
		p_priv->baud = baud_rate;
	}

	/* set CTS/RTS handshake etc. */
	p_priv->cflag = cflag;
	p_priv->flow_control = (cflag & CRTSCTS)? flow_cts: flow_none;

	keyspan_send_setup(port, 0);
}

static int keyspan_tiocmget(struct usb_serial_port *port, struct file *file)
{
	unsigned int			value;
	struct keyspan_port_private 	*p_priv;

	p_priv = usb_get_serial_port_data(port);

	value = ((p_priv->rts_state) ? TIOCM_RTS : 0) |
		((p_priv->dtr_state) ? TIOCM_DTR : 0) |
		((p_priv->cts_state) ? TIOCM_CTS : 0) |
		((p_priv->dsr_state) ? TIOCM_DSR : 0) |
		((p_priv->dcd_state) ? TIOCM_CAR : 0) |
		((p_priv->ri_state) ? TIOCM_RNG : 0);

	return value;
}

static int keyspan_tiocmset(struct usb_serial_port *port, struct file *file,
			    unsigned int set, unsigned int clear)
{
	struct keyspan_port_private 	*p_priv;

	p_priv = usb_get_serial_port_data(port);

	if (set & TIOCM_RTS)
		p_priv->rts_state = 1;
	if (set & TIOCM_DTR)
		p_priv->dtr_state = 1;

	if (clear & TIOCM_RTS)
		p_priv->rts_state = 0;
	if (clear & TIOCM_DTR)
		p_priv->dtr_state = 0;
	keyspan_send_setup(port, 0);
	return 0;
}

static int keyspan_ioctl(struct usb_serial_port *port, struct file *file,
			     unsigned int cmd, unsigned long arg)
{
	return -ENOIOCTLCMD;
}

	/* Write function is similar for the four protocols used
	   with only a minor change for usa90 (usa19hs) required */
static int keyspan_write(struct usb_serial_port *port,
			 const unsigned char *buf, int count)
{
	struct keyspan_port_private 	*p_priv;
	const struct keyspan_device_details	*d_details;
	int				flip;
	int 				left, todo;
	struct urb			*this_urb;
 	int 				err, maxDataLen, dataOffset;

	p_priv = usb_get_serial_port_data(port);
	d_details = p_priv->device_details;

	if (d_details->msg_format == msg_usa90) {
   		maxDataLen = 64;
		dataOffset = 0;
	} else {
		maxDataLen = 63;
		dataOffset = 1;
	}

	dbg("%s - for port %d (%d chars), flip=%d",
	    __FUNCTION__, port->number, count, p_priv->out_flip);

	for (left = count; left > 0; left -= todo) {
		todo = left;
		if (todo > maxDataLen)
			todo = maxDataLen;

		flip = p_priv->out_flip;

		/* Check we have a valid urb/endpoint before we use it... */
		if ((this_urb = p_priv->out_urbs[flip]) == NULL) {
			/* no bulk out, so return 0 bytes written */
			dbg("%s - no output urb :(", __FUNCTION__);
			return count;
		}

		dbg("%s - endpoint %d flip %d", __FUNCTION__, usb_pipeendpoint(this_urb->pipe), flip);

		if (this_urb->status == -EINPROGRESS) {
			if (time_before(jiffies, p_priv->tx_start_time[flip] + 10 * HZ))
				break;
			usb_unlink_urb(this_urb);
			break;
		}

		/* First byte in buffer is "last flag" (except for usa19hx) - unused so
		   for now so set to zero */
		((char *)this_urb->transfer_buffer)[0] = 0;

		memcpy (this_urb->transfer_buffer + dataOffset, buf, todo);
		buf += todo;

		/* send the data out the bulk port */
		this_urb->transfer_buffer_length = todo + dataOffset;

		this_urb->dev = port->serial->dev;
		if ((err = usb_submit_urb(this_urb, GFP_ATOMIC)) != 0) {
			dbg("usb_submit_urb(write bulk) failed (%d)", err);
		}
		p_priv->tx_start_time[flip] = jiffies;

		/* Flip for next time if usa26 or usa28 interface
		   (not used on usa49) */
		p_priv->out_flip = (flip + 1) & d_details->outdat_endp_flip;
	}

	return count - left;
}

static void	usa26_indat_callback(struct urb *urb, struct pt_regs *regs)
{
	int			i, err;
	int			endpoint;
	struct usb_serial_port	*port;
	struct tty_struct	*tty;
	unsigned char 		*data = urb->transfer_buffer;

	dbg ("%s", __FUNCTION__);

	endpoint = usb_pipeendpoint(urb->pipe);

	if (urb->status) {
		dbg("%s - nonzero status: %x on endpoint %d.",
		    __FUNCTION__, urb->status, endpoint);
		return;
	}

	port = (struct usb_serial_port *) urb->context;
	tty = port->tty;
	if (urb->actual_length) {
		/* 0x80 bit is error flag */
		if ((data[0] & 0x80) == 0) {
			/* no errors on individual bytes, only possible overrun err*/
			if (data[0] & RXERROR_OVERRUN)
					err = TTY_OVERRUN;
			else err = 0;
			for (i = 1; i < urb->actual_length ; ++i) {
				tty_insert_flip_char(tty, data[i], err);
			}
		} else {
			/* some bytes had errors, every byte has status */
			dbg("%s - RX error!!!!", __FUNCTION__);
			for (i = 0; i + 1 < urb->actual_length; i += 2) {
				int stat = data[i], flag = 0;
				if (stat & RXERROR_OVERRUN)
					flag |= TTY_OVERRUN;
				if (stat & RXERROR_FRAMING)
					flag |= TTY_FRAME;
				if (stat & RXERROR_PARITY)
					flag |= TTY_PARITY;
				/* XXX should handle break (0x10) */
				tty_insert_flip_char(tty, data[i+1], flag);
			}
		}
		tty_flip_buffer_push(tty);
	}

		/* Resubmit urb so we continue receiving */
	urb->dev = port->serial->dev;
	if (port->open_count)
		if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
			dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
		}
	return;
}

 	/* Outdat handling is common for all devices */
static void	usa2x_outdat_callback(struct urb *urb, struct pt_regs *regs)
{
	struct usb_serial_port *port;
	struct keyspan_port_private *p_priv;

	port = (struct usb_serial_port *) urb->context;
	p_priv = usb_get_serial_port_data(port);
	dbg ("%s - urb %d", __FUNCTION__, urb == p_priv->out_urbs[1]);

	if (port->open_count)
		schedule_work(&port->work);
}

static void	usa26_inack_callback(struct urb *urb, struct pt_regs *regs)
{
	dbg ("%s", __FUNCTION__);

}

static void	usa26_outcont_callback(struct urb *urb, struct pt_regs *regs)
{
	struct usb_serial_port *port;
	struct keyspan_port_private *p_priv;

	port = (struct usb_serial_port *) urb->context;
	p_priv = usb_get_serial_port_data(port);

	if (p_priv->resend_cont) {
		dbg ("%s - sending setup", __FUNCTION__);
		keyspan_usa26_send_setup(port->serial, port, p_priv->resend_cont - 1);
	}
}

static void	usa26_instat_callback(struct urb *urb, struct pt_regs *regs)
{
	unsigned char 				*data = urb->transfer_buffer;
	struct keyspan_usa26_portStatusMessage	*msg;
	struct usb_serial			*serial;
	struct usb_serial_port			*port;
	struct keyspan_port_private	 	*p_priv;
	int old_dcd_state, err;

	serial = (struct usb_serial *) urb->context;

	if (urb->status) {
		dbg("%s - nonzero status: %x", __FUNCTION__, urb->status);
		return;
	}
	if (urb->actual_length != 9) {
		dbg("%s - %d byte report??", __FUNCTION__, urb->actual_length);
		goto exit;
	}

	msg = (struct keyspan_usa26_portStatusMessage *)data;

#if 0
	dbg("%s - port status: port %d cts %d dcd %d dsr %d ri %d toff %d txoff %d rxen %d cr %d",
	    __FUNCTION__, msg->port, msg->hskia_cts, msg->gpia_dcd, msg->dsr, msg->ri, msg->_txOff,
	    msg->_txXoff, msg->rxEnabled, msg->controlResponse);
#endif

	/* Now do something useful with the data */

	/* Check port number from message and retrieve private data */
	if (msg->port >= serial->num_ports) {
		dbg ("%s - Unexpected port number %d", __FUNCTION__, msg->port);
		goto exit;
	}
	port = serial->port[msg->port];
	p_priv = usb_get_serial_port_data(port);

	/* Update handshaking pin state information */
	old_dcd_state = p_priv->dcd_state;
	p_priv->cts_state = ((msg->hskia_cts) ? 1 : 0);
	p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
	p_priv->dcd_state = ((msg->gpia_dcd) ? 1 : 0);
	p_priv->ri_state = ((msg->ri) ? 1 : 0);

	if (port->tty && !C_CLOCAL(port->tty)
	    && old_dcd_state != p_priv->dcd_state) {
		if (old_dcd_state)
			tty_hangup(port->tty);
		/*  else */
		/*	wake_up_interruptible(&p_priv->open_wait); */
	}

	/* Resubmit urb so we continue receiving */
	urb->dev = serial->dev;
	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
		dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
	}
exit: ;
}

static void	usa26_glocont_callback(struct urb *urb, struct pt_regs *regs)
{
	dbg ("%s", __FUNCTION__);

}

static void usa28_indat_callback(struct urb *urb, struct pt_regs *regs)
{
	int                     i, err;
	struct usb_serial_port  *port;
	struct tty_struct       *tty;
	unsigned char           *data;
	struct keyspan_port_private             *p_priv;

	dbg ("%s", __FUNCTION__);

	port = (struct usb_serial_port *) urb->context;
	p_priv = usb_get_serial_port_data(port);
	data = urb->transfer_buffer;

	if (urb != p_priv->in_urbs[p_priv->in_flip])
		return;

	do {
		if (urb->status) {
			dbg("%s - nonzero status: %x on endpoint %d.",
			    __FUNCTION__, urb->status, usb_pipeendpoint(urb->pipe));
			return;
		}

		port = (struct usb_serial_port *) urb->context;
		p_priv = usb_get_serial_port_data(port);
		data = urb->transfer_buffer;

		tty = port->tty;
		if (urb->actual_length) {
			for (i = 0; i < urb->actual_length ; ++i) {
				tty_insert_flip_char(tty, data[i], 0);
			}
			tty_flip_buffer_push(tty);
		}

		/* Resubmit urb so we continue receiving */
		urb->dev = port->serial->dev;
		if (port->open_count)
			if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
				dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
			}
		p_priv->in_flip ^= 1;

		urb = p_priv->in_urbs[p_priv->in_flip];
	} while (urb->status != -EINPROGRESS);
}

static void	usa28_inack_callback(struct urb *urb, struct pt_regs *regs)
{
	dbg ("%s", __FUNCTION__);
}

static void	usa28_outcont_callback(struct urb *urb, struct pt_regs *regs)
{
	struct usb_serial_port *port;
	struct keyspan_port_private *p_priv;

	port = (struct usb_serial_port *) urb->context;
	p_priv = usb_get_serial_port_data(port);

	if (p_priv->resend_cont) {
		dbg ("%s - sending setup", __FUNCTION__);
		keyspan_usa28_send_setup(port->serial, port, p_priv->resend_cont - 1);
	}
}

static void	usa28_instat_callback(struct urb *urb, struct pt_regs *regs)
{
	int					err;
	unsigned char 				*data = urb->transfer_buffer;
	struct keyspan_usa28_portStatusMessage	*msg;
	struct usb_serial			*serial;
	struct usb_serial_port			*port;
	struct keyspan_port_private	 	*p_priv;
	int old_dcd_state;

	serial = (struct usb_serial *) urb->context;

	if (urb->status) {
		dbg("%s - nonzero status: %x", __FUNCTION__, urb->status);
		return;
	}

	if (urb->actual_length != sizeof(struct keyspan_usa28_portStatusMessage)) {
		dbg("%s - bad length %d", __FUNCTION__, urb->actual_length);
		goto exit;
	}

	/*dbg("%s %x %x %x %x %x %x %x %x %x %x %x %x", __FUNCTION__
	    data[0], data[1], data[2], data[3], data[4], data[5],
	    data[6], data[7], data[8], data[9], data[10], data[11]);*/

		/* Now do something useful with the data */
	msg = (struct keyspan_usa28_portStatusMessage *)data;

		/* Check port number from message and retrieve private data */
	if (msg->port >= serial->num_ports) {
		dbg ("%s - Unexpected port number %d", __FUNCTION__, msg->port);
		goto exit;
	}
	port = serial->port[msg->port];
	p_priv = usb_get_serial_port_data(port);

	/* Update handshaking pin state information */
	old_dcd_state = p_priv->dcd_state;
	p_priv->cts_state = ((msg->cts) ? 1 : 0);
	p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
	p_priv->dcd_state = ((msg->dcd) ? 1 : 0);
	p_priv->ri_state = ((msg->ri) ? 1 : 0);

	if (port->tty && !C_CLOCAL(port->tty)
	    && old_dcd_state != p_priv->dcd_state) {
		if (old_dcd_state)
			tty_hangup(port->tty);
		/*  else */
		/*	wake_up_interruptible(&p_priv->open_wait); */
	}

		/* Resubmit urb so we continue receiving */
	urb->dev = serial->dev;
	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
		dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
	}
exit: ;
}

static void	usa28_glocont_callback(struct urb *urb, struct pt_regs *regs)
{
	dbg ("%s", __FUNCTION__);
}

static void	usa49_glocont_callback(struct urb *urb, struct pt_regs *regs)
{
	struct usb_serial *serial;
	struct usb_serial_port *port;
	struct keyspan_port_private *p_priv;
	int i;

	dbg ("%s", __FUNCTION__);

	serial = (struct usb_serial *) urb->context;
	for (i = 0; i < serial->num_ports; ++i) {
		port = serial->port[i];
		p_priv = usb_get_serial_port_data(port);

		if (p_priv->resend_cont) {
			dbg ("%s - sending setup", __FUNCTION__);
			keyspan_usa49_send_setup(serial, port, p_priv->resend_cont - 1);
			break;
		}
	}
}

	/* This is actually called glostat in the Keyspan
	   doco */
static void	usa49_instat_callback(struct urb *urb, struct pt_regs *regs)
{
	int					err;
	unsigned char 				*data = urb->transfer_buffer;
	struct keyspan_usa49_portStatusMessage	*msg;
	struct usb_serial			*serial;
	struct usb_serial_port			*port;
	struct keyspan_port_private	 	*p_priv;
	int old_dcd_state;

	dbg ("%s", __FUNCTION__);

	serial = (struct usb_serial *) urb->context;

	if (urb->status) {
		dbg("%s - nonzero status: %x", __FUNCTION__, urb->status);
		return;
	}

	if (urb->actual_length != sizeof(struct keyspan_usa49_portStatusMessage)) {
		dbg("%s - bad length %d", __FUNCTION__, urb->actual_length);
		goto exit;
	}

	/*dbg(" %x %x %x %x %x %x %x %x %x %x %x", __FUNCTION__,
	    data[0], data[1], data[2], data[3], data[4], data[5],
	    data[6], data[7], data[8], data[9], data[10]);*/

		/* Now do something useful with the data */
	msg = (struct keyspan_usa49_portStatusMessage *)data;

		/* Check port number from message and retrieve private data */
	if (msg->portNumber >= serial->num_ports) {
		dbg ("%s - Unexpected port number %d", __FUNCTION__, msg->portNumber);
		goto exit;
	}
	port = serial->port[msg->portNumber];
	p_priv = usb_get_serial_port_data(port);

	/* Update handshaking pin state information */
	old_dcd_state = p_priv->dcd_state;
	p_priv->cts_state = ((msg->cts) ? 1 : 0);
	p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
	p_priv->dcd_state = ((msg->dcd) ? 1 : 0);
	p_priv->ri_state = ((msg->ri) ? 1 : 0);

	if (port->tty && !C_CLOCAL(port->tty)
	    && old_dcd_state != p_priv->dcd_state) {
		if (old_dcd_state)
			tty_hangup(port->tty);
		/*  else */
		/*	wake_up_interruptible(&p_priv->open_wait); */
	}

		/* Resubmit urb so we continue receiving */
	urb->dev = serial->dev;

	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
		dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
	}
exit:	;
}

static void	usa49_inack_callback(struct urb *urb, struct pt_regs *regs)
{
	dbg ("%s", __FUNCTION__);
}

static void	usa49_indat_callback(struct urb *urb, struct pt_regs *regs)
{
	int			i, err;
	int			endpoint;
	struct usb_serial_port	*port;
	struct tty_struct	*tty;
	unsigned char 		*data = urb->transfer_buffer;

	dbg ("%s", __FUNCTION__);

	endpoint = usb_pipeendpoint(urb->pipe);

	if (urb->status) {
		dbg("%s - nonzero status: %x on endpoint %d.", __FUNCTION__,
		    urb->status, endpoint);
		return;
	}

	port = (struct usb_serial_port *) urb->context;
	tty = port->tty;
	if (urb->actual_length) {
		/* 0x80 bit is error flag */
		if ((data[0] & 0x80) == 0) {
			/* no error on any byte */
			for (i = 1; i < urb->actual_length ; ++i) {
				tty_insert_flip_char(tty, data[i], 0);
			}
		} else {
			/* some bytes had errors, every byte has status */
			for (i = 0; i + 1 < urb->actual_length; i += 2) {
				int stat = data[i], flag = 0;
				if (stat & RXERROR_OVERRUN)
					flag |= TTY_OVERRUN;
				if (stat & RXERROR_FRAMING)
					flag |= TTY_FRAME;
				if (stat & RXERROR_PARITY)
					flag |= TTY_PARITY;
				/* XXX should handle break (0x10) */
				tty_insert_flip_char(tty, data[i+1], flag);
			}
		}
		tty_flip_buffer_push(tty);
	}

		/* Resubmit urb so we continue receiving */
	urb->dev = port->serial->dev;
	if (port->open_count)
		if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
			dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
		}
}

/* not used, usa-49 doesn't have per-port control endpoints */
static void	usa49_outcont_callback(struct urb *urb, struct pt_regs *regs)
{
	dbg ("%s", __FUNCTION__);
}

static void	usa90_indat_callback(struct urb *urb, struct pt_regs *regs)
{
	int			i, err;
	int			endpoint;
	struct usb_serial_port	*port;
	struct keyspan_port_private	 	*p_priv;
	struct tty_struct	*tty;
	unsigned char 		*data = urb->transfer_buffer;

	dbg ("%s", __FUNCTION__);

	endpoint = usb_pipeendpoint(urb->pipe);

	if (urb->status) {
		dbg("%s - nonzero status: %x on endpoint %d.",
		    __FUNCTION__, urb->status, endpoint);
		return;
	}

	port = (struct usb_serial_port *) urb->context;
	p_priv = usb_get_serial_port_data(port);

	tty = port->tty;
	if (urb->actual_length) {

		/* if current mode is DMA, looks like usa28 format
	   		otherwise looks like usa26 data format */

		if (p_priv->baud > 57600) {
			for (i = 0; i < urb->actual_length ; ++i)
				tty_insert_flip_char(tty, data[i], 0);
		}
		else {

			/* 0x80 bit is error flag */
			if ((data[0] & 0x80) == 0) {
				/* no errors on individual bytes, only possible overrun err*/
				if (data[0] & RXERROR_OVERRUN)
						err = TTY_OVERRUN;
				else err = 0;
				for (i = 1; i < urb->actual_length ; ++i)
					tty_insert_flip_char(tty, data[i], err);

			}
			else {
			/* some bytes had errors, every byte has status */
				dbg("%s - RX error!!!!", __FUNCTION__);
				for (i = 0; i + 1 < urb->actual_length; i += 2) {
					int stat = data[i], flag = 0;
					if (stat & RXERROR_OVERRUN)
						flag |= TTY_OVERRUN;
					if (stat & RXERROR_FRAMING)
						flag |= TTY_FRAME;
					if (stat & RXERROR_PARITY)
						flag |= TTY_PARITY;
					/* XXX should handle break (0x10) */
					tty_insert_flip_char(tty, data[i+1], flag);
				}
			}
		}
		tty_flip_buffer_push(tty);
	}

	/* Resubmit urb so we continue receiving */
	urb->dev = port->serial->dev;
	if (port->open_count)
		if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
			dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
		}
	return;
}

static void	usa90_instat_callback(struct urb *urb, struct pt_regs *regs)
{
	unsigned char 				*data = urb->transfer_buffer;
	struct keyspan_usa90_portStatusMessage	*msg;
	struct usb_serial			*serial;
	struct usb_serial_port			*port;
	struct keyspan_port_private	 	*p_priv;
	int old_dcd_state, err;

	serial = (struct usb_serial *) urb->context;

	if (urb->status) {
		dbg("%s - nonzero status: %x", __FUNCTION__, urb->status);
		return;
	}
	if (urb->actual_length < 14) {
		dbg("%s - %d byte report??", __FUNCTION__, urb->actual_length);
		goto exit;
	}

	msg = (struct keyspan_usa90_portStatusMessage *)data;

	/* Now do something useful with the data */

	port = serial->port[0];
	p_priv = usb_get_serial_port_data(port);

	/* Update handshaking pin state information */
	old_dcd_state = p_priv->dcd_state;
	p_priv->cts_state = ((msg->cts) ? 1 : 0);
	p_priv->dsr_state = ((msg->dsr) ? 1 : 0);
	p_priv->dcd_state = ((msg->dcd) ? 1 : 0);
	p_priv->ri_state = ((msg->ri) ? 1 : 0);

	if (port->tty && !C_CLOCAL(port->tty)
	    && old_dcd_state != p_priv->dcd_state) {
		if (old_dcd_state)
			tty_hangup(port->tty);
		/*  else */
		/*	wake_up_interruptible(&p_priv->open_wait); */
	}

	/* Resubmit urb so we continue receiving */
	urb->dev = serial->dev;
	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) != 0) {
		dbg("%s - resubmit read urb failed. (%d)", __FUNCTION__, err);
	}
exit:
	;
}

static void	usa90_outcont_callback(struct urb *urb, struct pt_regs *regs)
{
	struct usb_serial_port *port;
	struct keyspan_port_private *p_priv;

	port = (struct usb_serial_port *) urb->context;
	p_priv = usb_get_serial_port_data(port);

	if (p_priv->resend_cont) {
		dbg ("%s - sending setup", __FUNCTION__);
		keyspan_usa90_send_setup(port->serial, port, p_priv->resend_cont - 1);
	}
}

static int keyspan_write_room (struct usb_serial_port *port)
{
	struct keyspan_port_private	*p_priv;
	const struct keyspan_device_details	*d_details;
	int				flip;
	int				data_len;
	struct urb			*this_urb;

	dbg("%s", __FUNCTION__);
	p_priv = usb_get_serial_port_data(port);
	d_details = p_priv->device_details;

	if (d_details->msg_format == msg_usa90)
   		data_len = 64;
	else
		data_len = 63;

	flip = p_priv->out_flip;

	/* Check both endpoints to see if any are available. */
	if ((this_urb = p_priv->out_urbs[flip]) != NULL) {
		if (this_urb->status != -EINPROGRESS)
			return (data_len);
		flip = (flip + 1) & d_details->outdat_endp_flip;
		if ((this_urb = p_priv->out_urbs[flip]) != NULL)
			if (this_urb->status != -EINPROGRESS)
				return (data_len);
	}
	return (0);
}

static int keyspan_chars_in_buffer (struct usb_serial_port *port)
{
	return (0);
}

static int keyspan_open (struct usb_serial_port *port, struct file *filp)
{
	struct keyspan_port_private 	*p_priv;
	struct keyspan_serial_private 	*s_priv;
	struct usb_serial 		*serial = port->serial;
	const struct keyspan_device_details	*d_details;
	int				i, err;
	int				baud_rate, device_port;
	struct urb			*urb;
	unsigned int			cflag;

	s_priv = usb_get_serial_data(serial);
	p_priv = usb_get_serial_port_data(port);
	d_details = p_priv->device_details;

	dbg("%s - port%d.", __FUNCTION__, port->number);

	/* Set some sane defaults */
	p_priv->rts_state = 1;
	p_priv->dtr_state = 1;
	p_priv->baud = 9600;

	/* force baud and lcr to be set on open */
	p_priv->old_baud = 0;
	p_priv->old_cflag = 0;

	p_priv->out_flip = 0;
	p_priv->in_flip = 0;

	/* Reset low level data toggle and start reading from endpoints */
	for (i = 0; i < 2; i++) {
		if ((urb = p_priv->in_urbs[i]) == NULL)
			continue;
		urb->dev = serial->dev;

		/* make sure endpoint data toggle is synchronized with the device */

		usb_clear_halt(urb->dev, urb->pipe);

		if ((err = usb_submit_urb(urb, GFP_KERNEL)) != 0) {
			dbg("%s - submit urb %d failed (%d)", __FUNCTION__, i, err);
		}
	}

	/* Reset low level data toggle on out endpoints */
	for (i = 0; i < 2; i++) {
		if ((urb = p_priv->out_urbs[i]) == NULL)
			continue;
		urb->dev = serial->dev;
		/* usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), 0); */
	}

	/* get the terminal config for the setup message now so we don't
	 * need to send 2 of them */

	cflag = port->tty->termios->c_cflag;
	device_port = port->number - port->serial->minor;

	/* Baud rate calculation takes baud rate as an integer
	   so other rates can be generated if desired. */
	baud_rate = tty_get_baud_rate(port->tty);
	/* If no match or invalid, leave as default */
	if (baud_rate >= 0
	    && d_details->calculate_baud_rate(baud_rate, d_details->baudclk,
				NULL, NULL, NULL, device_port) == KEYSPAN_BAUD_RATE_OK) {
		p_priv->baud = baud_rate;
	}

	/* set CTS/RTS handshake etc. */
	p_priv->cflag = cflag;
	p_priv->flow_control = (cflag & CRTSCTS)? flow_cts: flow_none;

	keyspan_send_setup(port, 1);
	//mdelay(100);
	//keyspan_set_termios(port, NULL);

	return (0);
}

static inline void stop_urb(struct urb *urb)
{
	if (urb && urb->status == -EINPROGRESS)
		usb_kill_urb(urb);
}

static void keyspan_close(struct usb_serial_port *port, struct file *filp)
{
	int			i;
	struct usb_serial	*serial = port->serial;
	struct keyspan_serial_private 	*s_priv;
	struct keyspan_port_private 	*p_priv;

	dbg("%s", __FUNCTION__);
	s_priv = usb_get_serial_data(serial);
	p_priv = usb_get_serial_port_data(port);

	p_priv->rts_state = 0;
	p_priv->dtr_state = 0;

	if (serial->dev) {
		keyspan_send_setup(port, 2);
		/* pilot-xfer seems to work best with this delay */
		mdelay(100);
		// keyspan_set_termios(port, NULL);
	}

	/*while (p_priv->outcont_urb->status == -EINPROGRESS) {
		dbg("%s - urb in progress", __FUNCTION__);
	}*/

	p_priv->out_flip = 0;
	p_priv->in_flip = 0;

	if (serial->dev) {
		/* Stop reading/writing urbs */
		stop_urb(p_priv->inack_urb);
		/* stop_urb(p_priv->outcont_urb); */
		for (i = 0; i < 2; i++) {
			stop_urb(p_priv->in_urbs[i]);
			stop_urb(p_priv->out_urbs[i]);
		}
	}
	port->tty = NULL;
}

	/* download the firmware to a pre-renumeration device */
static int keyspan_fake_startup (struct usb_serial *serial)
{
	int 				response;
	const struct ezusb_hex_record 	*record;
	char				*fw_name;

	dbg("Keyspan startup version %04x product %04x",
	    le16_to_cpu(serial->dev->descriptor.bcdDevice),
	    le16_to_cpu(serial->dev->descriptor.idProduct));

	if ((le16_to_cpu(serial->dev->descriptor.bcdDevice) & 0x8000) != 0x8000) {
		dbg("Firmware already loaded.  Quitting.");
		return(1);
	}

		/* Select firmware image on the basis of idProduct */
	switch (le16_to_cpu(serial->dev->descriptor.idProduct)) {
	case keyspan_usa28_pre_product_id:
		record = &keyspan_usa28_firmware[0];
		fw_name = "USA28";
		break;

	case keyspan_usa28x_pre_product_id:
		record = &keyspan_usa28x_firmware[0];
		fw_name = "USA28X";
		break;

	case keyspan_usa28xa_pre_product_id:
		record = &keyspan_usa28xa_firmware[0];
		fw_name = "USA28XA";
		break;

	case keyspan_usa28xb_pre_product_id:
		record = &keyspan_usa28xb_firmware[0];
		fw_name = "USA28XB";
		break;

	case keyspan_usa19_pre_product_id:
		record = &keyspan_usa19_firmware[0];
		fw_name = "USA19";
		break;

	case keyspan_usa19qi_pre_product_id:
		record = &keyspan_usa19qi_firmware[0];
		fw_name = "USA19QI";
		break;

	case keyspan_mpr_pre_product_id:
		record = &keyspan_mpr_firmware[0];
		fw_name = "MPR";
		break;

	case keyspan_usa19qw_pre_product_id:
		record = &keyspan_usa19qw_firmware[0];
		fw_name = "USA19QI";
		break;

	case keyspan_usa18x_pre_product_id:
		record = &keyspan_usa18x_firmware[0];
		fw_name = "USA18X";
		break;

	case keyspan_usa19w_pre_product_id:
		record = &keyspan_usa19w_firmware[0];
		fw_name = "USA19W";
		break;

	case keyspan_usa49w_pre_product_id:
		record = &keyspan_usa49w_firmware[0];
		fw_name = "USA49W";
		break;

	case keyspan_usa49wlc_pre_product_id:
		record = &keyspan_usa49wlc_firmware[0];
		fw_name = "USA49WLC";
		break;

	default:
		record = NULL;
		fw_name = "Unknown";
		break;
	}

	if (record == NULL) {
		dev_err(&serial->dev->dev, "Required keyspan firmware image (%s) unavailable.\n", fw_name);
		return(1);
	}

	dbg("Uploading Keyspan %s firmware.", fw_name);

		/* download the firmware image */
	response = ezusb_set_reset(serial, 1);

	while(record->address != 0xffff) {
		response = ezusb_writememory(serial, record->address,
					     (unsigned char *)record->data,
					     record->data_size, 0xa0);
		if (response < 0) {
			dev_err(&serial->dev->dev, "ezusb_writememory failed for Keyspan"
				"firmware (%d %04X %p %d)\n",
				response,
				record->address, record->data, record->data_size);
			break;
		}
		record++;
	}
		/* bring device out of reset. Renumeration will occur in a
		   moment and the new device will bind to the real driver */
	response = ezusb_set_reset(serial, 0);

	/* we don't want this device to have a driver assigned to it. */
	return (1);
}

/* Helper functions used by keyspan_setup_urbs */
static struct urb *keyspan_setup_urb (struct usb_serial *serial, int endpoint,
				      int dir, void *ctx, char *buf, int len,
				      void (*callback)(struct urb *, struct pt_regs *regs))
{
	struct urb *urb;

	if (endpoint == -1)
		return NULL;		/* endpoint not needed */

	dbg ("%s - alloc for endpoint %d.", __FUNCTION__, endpoint);
	urb = usb_alloc_urb(0, GFP_KERNEL);		/* No ISO */
	if (urb == NULL) {
		dbg ("%s - alloc for endpoint %d failed.", __FUNCTION__, endpoint);
		return NULL;
	}

		/* Fill URB using supplied data. */
	usb_fill_bulk_urb(urb, serial->dev,
		      usb_sndbulkpipe(serial->dev, endpoint) | dir,
		      buf, len, callback, ctx);

	return urb;
}

static struct callbacks {
	void	(*instat_callback)(struct urb *, struct pt_regs *regs);
	void	(*glocont_callback)(struct urb *, struct pt_regs *regs);
	void	(*indat_callback)(struct urb *, struct pt_regs *regs);
	void	(*outdat_callback)(struct urb *, struct pt_regs *regs);
	void	(*inack_callback)(struct urb *, struct pt_regs *regs);
	void	(*outcont_callback)(struct urb *, struct pt_regs *regs);
} keyspan_callbacks[] = {
	{
		/* msg_usa26 callbacks */
		.instat_callback =	usa26_instat_callback,
		.glocont_callback =	usa26_glocont_callback,
		.indat_callback =	usa26_indat_callback,
		.outdat_callback =	usa2x_outdat_callback,
		.inack_callback =	usa26_inack_callback,
		.outcont_callback =	usa26_outcont_callback,
	}, {
		/* msg_usa28 callbacks */
		.instat_callback =	usa28_instat_callback,
		.glocont_callback =	usa28_glocont_callback,
		.indat_callback =	usa28_indat_callback,
		.outdat_callback =	usa2x_outdat_callback,
		.inack_callback =	usa28_inack_callback,
		.outcont_callback =	usa28_outcont_callback,
	}, {
		/* msg_usa49 callbacks */
		.instat_callback =	usa49_instat_callback,
		.glocont_callback =	usa49_glocont_callback,
		.indat_callback =	usa49_indat_callback,
		.outdat_callback =	usa2x_outdat_callback,
		.inack_callback =	usa49_inack_callback,
		.outcont_callback =	usa49_outcont_callback,
	}, {
		/* msg_usa90 callbacks */
		.instat_callback =	usa90_instat_callback,
		.glocont_callback =	usa28_glocont_callback,
		.indat_callback =	usa90_indat_callback,
		.outdat_callback =	usa2x_outdat_callback,
		.inack_callback =	usa28_inack_callback,
		.outcont_callback =	usa90_outcont_callback,
	}
};

	/* Generic setup urbs function that uses
	   data in device_details */
static void keyspan_setup_urbs(struct usb_serial *serial)
{
	int				i, j;
	struct keyspan_serial_private 	*s_priv;
	const struct keyspan_device_details	*d_details;
	struct usb_serial_port		*port;
	struct keyspan_port_private	*p_priv;
	struct callbacks		*cback;
	int				endp;

	dbg ("%s", __FUNCTION__);

	s_priv = usb_get_serial_data(serial);
	d_details = s_priv->device_details;

		/* Setup values for the various callback routines */
	cback = &keyspan_callbacks[d_details->msg_format];

		/* Allocate and set up urbs for each one that is in use,
		   starting with instat endpoints */
	s_priv->instat_urb = keyspan_setup_urb
		(serial, d_details->instat_endpoint, USB_DIR_IN,
		 serial, s_priv->instat_buf, INSTAT_BUFLEN,
		 cback->instat_callback);

	s_priv->glocont_urb = keyspan_setup_urb
		(serial, d_details->glocont_endpoint, USB_DIR_OUT,
		 serial, s_priv->glocont_buf, GLOCONT_BUFLEN,
		 cback->glocont_callback);

		/* Setup endpoints for each port specific thing */
	for (i = 0; i < d_details->num_ports; i ++) {
		port = serial->port[i];
		p_priv = usb_get_serial_port_data(port);

		/* Do indat endpoints first, once for each flip */
		endp = d_details->indat_endpoints[i];
		for (j = 0; j <= d_details->indat_endp_flip; ++j, ++endp) {
			p_priv->in_urbs[j] = keyspan_setup_urb
				(serial, endp, USB_DIR_IN, port,
				 p_priv->in_buffer[j], 64,
				 cback->indat_callback);
		}
		for (; j < 2; ++j)
			p_priv->in_urbs[j] = NULL;

		/* outdat endpoints also have flip */
		endp = d_details->outdat_endpoints[i];
		for (j = 0; j <= d_details->outdat_endp_flip; ++j, ++endp) {
			p_priv->out_urbs[j] = keyspan_setup_urb
				(serial, endp, USB_DIR_OUT, port,
				 p_priv->out_buffer[j], 64,
				 cback->outdat_callback);
		}
		for (; j < 2; ++j)
			p_priv->out_urbs[j] = NULL;

		/* inack endpoint */
		p_priv->inack_urb = keyspan_setup_urb
			(serial, d_details->inack_endpoints[i], USB_DIR_IN,
			 port, p_priv->inack_buffer, 1, cback->inack_callback);

		/* outcont endpoint */
		p_priv->outcont_urb = keyspan_setup_urb
			(serial, d_details->outcont_endpoints[i], USB_DIR_OUT,
			 port, p_priv->outcont_buffer, 64,
			 cback->outcont_callback);
	}

}

/* usa19 function doesn't require prescaler */
static int keyspan_usa19_calc_baud(u32 baud_rate, u32 baudclk, u8 *rate_hi,
				   u8 *rate_low, u8 *prescaler, int portnum)
{
	u32 	b16,	/* baud rate times 16 (actual rate used internally) */
		div,	/* divisor */
		cnt;	/* inverse of divisor (programmed into 8051) */

	dbg ("%s - %d.", __FUNCTION__, baud_rate);

		/* prevent divide by zero...  */
	if( (b16 = (baud_rate * 16L)) == 0) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}

		/* Any "standard" rate over 57k6 is marginal on the USA-19
		   as we run out of divisor resolution. */
	if (baud_rate > 57600) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}

		/* calculate the divisor and the counter (its inverse) */
	if( (div = (baudclk / b16)) == 0) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}
	else {
		cnt = 0 - div;
	}

	if(div > 0xffff) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}

		/* return the counter values if non-null */
	if (rate_low) {
		*rate_low = (u8) (cnt & 0xff);
	}
	if (rate_hi) {
		*rate_hi = (u8) ((cnt >> 8) & 0xff);
	}
	if (rate_low && rate_hi) {
		dbg ("%s - %d %02x %02x.", __FUNCTION__, baud_rate, *rate_hi, *rate_low);
	}

	return (KEYSPAN_BAUD_RATE_OK);
}

/* usa19hs function doesn't require prescaler */
static int keyspan_usa19hs_calc_baud(u32 baud_rate, u32 baudclk, u8 *rate_hi,
				   u8 *rate_low, u8 *prescaler, int portnum)
{
	u32 	b16,	/* baud rate times 16 (actual rate used internally) */
			div;	/* divisor */

	dbg ("%s - %d.", __FUNCTION__, baud_rate);

		/* prevent divide by zero...  */
	if( (b16 = (baud_rate * 16L)) == 0)
		return (KEYSPAN_INVALID_BAUD_RATE);

		/* calculate the divisor */
	if( (div = (baudclk / b16)) == 0)
		return (KEYSPAN_INVALID_BAUD_RATE);

	if(div > 0xffff)
		return (KEYSPAN_INVALID_BAUD_RATE);

		/* return the counter values if non-null */
	if (rate_low)
		*rate_low = (u8) (div & 0xff);

	if (rate_hi)
		*rate_hi = (u8) ((div >> 8) & 0xff);

	if (rate_low && rate_hi)
		dbg ("%s - %d %02x %02x.", __FUNCTION__, baud_rate, *rate_hi, *rate_low);

	return (KEYSPAN_BAUD_RATE_OK);
}

static int keyspan_usa19w_calc_baud(u32 baud_rate, u32 baudclk, u8 *rate_hi,
				    u8 *rate_low, u8 *prescaler, int portnum)
{
	u32 	b16,	/* baud rate times 16 (actual rate used internally) */
		clk,	/* clock with 13/8 prescaler */
		div,	/* divisor using 13/8 prescaler */
		res,	/* resulting baud rate using 13/8 prescaler */
		diff,	/* error using 13/8 prescaler */
		smallest_diff;
	u8	best_prescaler;
	int	i;

	dbg ("%s - %d.", __FUNCTION__, baud_rate);

		/* prevent divide by zero */
	if( (b16 = baud_rate * 16L) == 0) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}

		/* Calculate prescaler by trying them all and looking
		   for best fit */

		/* start with largest possible difference */
	smallest_diff = 0xffffffff;

		/* 0 is an invalid prescaler, used as a flag */
	best_prescaler = 0;

	for(i = 8; i <= 0xff; ++i) {
		clk = (baudclk * 8) / (u32) i;

		if( (div = clk / b16) == 0) {
			continue;
		}

		res = clk / div;
		diff= (res > b16) ? (res-b16) : (b16-res);

		if(diff < smallest_diff) {
			best_prescaler = i;
			smallest_diff = diff;
		}
	}

	if(best_prescaler == 0) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}

	clk = (baudclk * 8) / (u32) best_prescaler;
	div = clk / b16;

		/* return the divisor and prescaler if non-null */
	if (rate_low) {
		*rate_low = (u8) (div & 0xff);
	}
	if (rate_hi) {
		*rate_hi = (u8) ((div >> 8) & 0xff);
	}
	if (prescaler) {
		*prescaler = best_prescaler;
		/*  dbg("%s - %d %d", __FUNCTION__, *prescaler, div); */
	}
	return (KEYSPAN_BAUD_RATE_OK);
}

	/* USA-28 supports different maximum baud rates on each port */
static int keyspan_usa28_calc_baud(u32 baud_rate, u32 baudclk, u8 *rate_hi,
				    u8 *rate_low, u8 *prescaler, int portnum)
{
	u32 	b16,	/* baud rate times 16 (actual rate used internally) */
		div,	/* divisor */
		cnt;	/* inverse of divisor (programmed into 8051) */

	dbg ("%s - %d.", __FUNCTION__, baud_rate);

		/* prevent divide by zero */
	if ((b16 = baud_rate * 16L) == 0)
		return (KEYSPAN_INVALID_BAUD_RATE);

		/* calculate the divisor and the counter (its inverse) */
	if ((div = (KEYSPAN_USA28_BAUDCLK / b16)) == 0) {
		return (KEYSPAN_INVALID_BAUD_RATE);
	}
	else {
		cnt = 0 - div;
	}

		/* check for out of range, based on portnum,
		   and return result */
	if(portnum == 0) {
		if(div > 0xffff)
			return (KEYSPAN_INVALID_BAUD_RATE);
	}
	else {
		if(portnum == 1) {
			if(div > 0xff) {
				return (KEYSPAN_INVALID_BAUD_RATE);
			}
		}
		else {
			return (KEYSPAN_INVALID_BAUD_RATE);
		}
	}

		/* return the counter values if not NULL
		   (port 1 will ignore retHi) */
	if (rate_low) {
		*rate_low = (u8) (cnt & 0xff);
	}
	if (rate_hi) {
		*rate_hi = (u8) ((cnt >> 8) & 0xff);
	}
	dbg ("%s - %d OK.", __FUNCTION__, baud_rate);
	return (KEYSPAN_BAUD_RATE_OK);
}

static int keyspan_usa26_send_setup(struct usb_serial *serial,
				    struct usb_serial_port *port,
				    int reset_port)
{
	struct keyspan_usa26_portControlMessage	msg;
	struct keyspan_serial_private 		*s_priv;
	struct keyspan_port_private 		*p_priv;
	const struct keyspan_device_details	*d_details;
	int 					outcont_urb;
	struct urb				*this_urb;
	int 					device_port, err;

	dbg ("%s reset=%d", __FUNCTION__, reset_port);

	s_priv = usb_get_serial_data(serial);
	p_priv = usb_get_serial_port_data(port);
	d_details = s_priv->device_details;
	device_port = port->number - port->serial->minor;

	outcont_urb = d_details->outcont_endpoints[port->number];
	this_urb = p_priv->outcont_urb;

	dbg("%s - endpoint %d", __FUNCTION__, usb_pipeendpoint(this_urb->pipe));

		/* Make sure we have an urb then send the message */
	if (this_urb == NULL) {
		dbg("%s - oops no urb.", __FUNCTION__);
		return -1;
	}

	/* Save reset port val for resend.
	Don't overwrite resend for close condition. */
	if (p_priv->resend_cont != 3)
		p_priv->resend_cont = reset_port + 1;
	if (this_urb->status == -EINPROGRESS) {
		/*  dbg ("%s - already writing", __FUNCTION__); */
		mdelay(5);
		return(-1);
	}

	memset(&msg, 0, sizeof (struct keyspan_usa26_portControlMessage));

		/* Only set baud rate if it's changed */
	if (p_priv->old_baud != p_priv->baud) {
		p_priv->old_baud = p_priv->baud;
		msg.setClocking = 0xff;
		if (d_details->calculate_baud_rate
		    (p_priv->baud, d_details->baudclk, &msg.baudHi,
		     &msg.baudLo, &msg.prescaler, device_port) == KEYSPAN_INVALID_BAUD_RATE ) {
			dbg("%s - Invalid baud rate %d requested, using 9600.", __FUNCTION__,
			    p_priv->baud);
			msg.baudLo = 0;
			msg.baudHi = 125;	/* Values for 9600 baud */
			msg.prescaler = 10;
		}
		msg.setPrescaler = 0xff;
	}

	msg.lcr = (p_priv->cflag & CSTOPB)? STOPBITS_678_2: STOPBITS_5678_1;
	switch (p_priv->cflag & CSIZE) {
	case CS5:
		msg.lcr |= USA_DATABITS_5;
		break;
	case CS6:
		msg.lcr |= USA_DATABITS_6;
		break;
	case CS7:
		msg.lcr |= USA_DATABITS_7;
		break;
	case CS8:
		msg.lcr |= USA_DATABITS_8;
		break;
	}
	if (p_priv->cflag & PARENB) {
		/* note USA_PARITY_NONE == 0 */
		msg.lcr |= (p_priv->cflag & PARODD)?
			USA_PARITY_ODD: USA_PARITY_EVEN;
	}
	msg.setLcr = 0xff;

	msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
	msg.xonFlowControl = 0;
	msg.setFlowControl = 0xff;
	msg.forwardingLength = 16;
	msg.xonChar = 17;
	msg.xoffChar = 19;

	/* Opening port */
	if (reset_port == 1) {
		msg._txOn = 1;
		msg._txOff = 0;
		msg.txFlush = 0;
		msg.txBreak = 0;
		msg.rxOn = 1;
		msg.rxOff = 0;
		msg.rxFlush = 1;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0xff;
	}

	/* Closing port */
	else if (reset_port == 2) {
		msg._txOn = 0;
		msg._txOff = 1;
		msg.txFlush = 0;
		msg.txBreak = 0;
		msg.rxOn = 0;
		msg.rxOff = 1;
		msg.rxFlush = 1;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0;
	}

	/* Sending intermediate configs */
	else {
		msg._txOn = (! p_priv->break_on);
		msg._txOff = 0;
		msg.txFlush = 0;
		msg.txBreak = (p_priv->break_on);
		msg.rxOn = 0;
		msg.rxOff = 0;
		msg.rxFlush = 0;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0x0;
	}

		/* Do handshaking outputs */
	msg.setTxTriState_setRts = 0xff;
	msg.txTriState_rts = p_priv->rts_state;

	msg.setHskoa_setDtr = 0xff;
	msg.hskoa_dtr = p_priv->dtr_state;

	p_priv->resend_cont = 0;
	memcpy (this_urb->transfer_buffer, &msg, sizeof(msg));

	/* send the data out the device on control endpoint */
	this_urb->transfer_buffer_length = sizeof(msg);

	this_urb->dev = serial->dev;
	if ((err = usb_submit_urb(this_urb, GFP_ATOMIC)) != 0) {
		dbg("%s - usb_submit_urb(setup) failed (%d)", __FUNCTION__, err);
	}
#if 0
	else {
		dbg("%s - usb_submit_urb(%d) OK %d bytes (end %d)", __FUNCTION__
		    outcont_urb, this_urb->transfer_buffer_length,
		    usb_pipeendpoint(this_urb->pipe));
	}
#endif

	return (0);
}

static int keyspan_usa28_send_setup(struct usb_serial *serial,
				    struct usb_serial_port *port,
				    int reset_port)
{
	struct keyspan_usa28_portControlMessage	msg;
	struct keyspan_serial_private	 	*s_priv;
	struct keyspan_port_private 		*p_priv;
	const struct keyspan_device_details	*d_details;
	struct urb				*this_urb;
	int 					device_port, err;

	dbg ("%s", __FUNCTION__);

	s_priv = usb_get_serial_data(serial);
	p_priv = usb_get_serial_port_data(port);
	d_details = s_priv->device_details;
	device_port = port->number - port->serial->minor;

	/* only do something if we have a bulk out endpoint */
	if ((this_urb = p_priv->outcont_urb) == NULL) {
		dbg("%s - oops no urb.", __FUNCTION__);
		return -1;
	}

	/* Save reset port val for resend.
	   Don't overwrite resend for close condition. */
	if (p_priv->resend_cont != 3)
		p_priv->resend_cont = reset_port + 1;
	if (this_urb->status == -EINPROGRESS) {
		dbg ("%s already writing", __FUNCTION__);
		mdelay(5);
		return(-1);
	}

	memset(&msg, 0, sizeof (struct keyspan_usa28_portControlMessage));

	msg.setBaudRate = 1;
	if (d_details->calculate_baud_rate(p_priv->baud, d_details->baudclk,
		&msg.baudHi, &msg.baudLo, NULL, device_port) == KEYSPAN_INVALID_BAUD_RATE ) {
		dbg("%s - Invalid baud rate requested %d.", __FUNCTION__, p_priv->baud);
		msg.baudLo = 0xff;
		msg.baudHi = 0xb2;	/* Values for 9600 baud */
	}

	/* If parity is enabled, we must calculate it ourselves. */
	msg.parity = 0;		/* XXX for now */

	msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
	msg.xonFlowControl = 0;

	/* Do handshaking outputs, DTR is inverted relative to RTS */
	msg.rts = p_priv->rts_state;
	msg.dtr = p_priv->dtr_state;

	msg.forwardingLength = 16;
	msg.forwardMs = 10;
	msg.breakThreshold = 45;
	msg.xonChar = 17;
	msg.xoffChar = 19;

	/*msg.returnStatus = 1;
	msg.resetDataToggle = 0xff;*/
	/* Opening port */
	if (reset_port == 1) {
		msg._txOn = 1;
		msg._txOff = 0;
		msg.txFlush = 0;
		msg.txForceXoff = 0;
		msg.txBreak = 0;
		msg.rxOn = 1;
		msg.rxOff = 0;
		msg.rxFlush = 1;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0xff;
	}
	/* Closing port */
	else if (reset_port == 2) {
		msg._txOn = 0;
		msg._txOff = 1;
		msg.txFlush = 0;
		msg.txForceXoff = 0;
		msg.txBreak = 0;
		msg.rxOn = 0;
		msg.rxOff = 1;
		msg.rxFlush = 1;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0;
	}
	/* Sending intermediate configs */
	else {
		msg._txOn = (! p_priv->break_on);
		msg._txOff = 0;
		msg.txFlush = 0;
		msg.txForceXoff = 0;
		msg.txBreak = (p_priv->break_on);
		msg.rxOn = 0;
		msg.rxOff = 0;
		msg.rxFlush = 0;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0x0;
	}

	p_priv->resend_cont = 0;
	memcpy (this_urb->transfer_buffer, &msg, sizeof(msg));

	/* send the data out the device on control endpoint */
	this_urb->transfer_buffer_length = sizeof(msg);

	this_urb->dev = serial->dev;
	if ((err = usb_submit_urb(this_urb, GFP_ATOMIC)) != 0) {
		dbg("%s - usb_submit_urb(setup) failed", __FUNCTION__);
	}
#if 0
	else {
		dbg("%s - usb_submit_urb(setup) OK %d bytes", __FUNCTION__,
		    this_urb->transfer_buffer_length);
	}
#endif

	return (0);
}

static int keyspan_usa49_send_setup(struct usb_serial *serial,
				    struct usb_serial_port *port,
				    int reset_port)
{
	struct keyspan_usa49_portControlMessage	msg;
	struct keyspan_serial_private 		*s_priv;
	struct keyspan_port_private 		*p_priv;
	const struct keyspan_device_details	*d_details;
	int 					glocont_urb;
	struct urb				*this_urb;
	int 					err, device_port;

	dbg ("%s", __FUNCTION__);

	s_priv = usb_get_serial_data(serial);
	p_priv = usb_get_serial_port_data(port);
	d_details = s_priv->device_details;

	glocont_urb = d_details->glocont_endpoint;
	this_urb = s_priv->glocont_urb;

		/* Work out which port within the device is being setup */
	device_port = port->number - port->serial->minor;

	dbg("%s - endpoint %d port %d (%d)",__FUNCTION__, usb_pipeendpoint(this_urb->pipe), port->number, device_port);

		/* Make sure we have an urb then send the message */
	if (this_urb == NULL) {
		dbg("%s - oops no urb for port %d.", __FUNCTION__, port->number);
		return -1;
	}

	/* Save reset port val for resend.
	   Don't overwrite resend for close condition. */
	if (p_priv->resend_cont != 3)
		p_priv->resend_cont = reset_port + 1;
	if (this_urb->status == -EINPROGRESS) {
		/*  dbg ("%s - already writing", __FUNCTION__); */
		mdelay(5);
		return(-1);
	}

	memset(&msg, 0, sizeof (struct keyspan_usa49_portControlMessage));

	/*msg.portNumber = port->number;*/
	msg.portNumber = device_port;

		/* Only set baud rate if it's changed */
	if (p_priv->old_baud != p_priv->baud) {
		p_priv->old_baud = p_priv->baud;
		msg.setClocking = 0xff;
		if (d_details->calculate_baud_rate
		    (p_priv->baud, d_details->baudclk, &msg.baudHi,
		     &msg.baudLo, &msg.prescaler, device_port) == KEYSPAN_INVALID_BAUD_RATE ) {
			dbg("%s - Invalid baud rate %d requested, using 9600.", __FUNCTION__,
			    p_priv->baud);
			msg.baudLo = 0;
			msg.baudHi = 125;	/* Values for 9600 baud */
			msg.prescaler = 10;
		}
		//msg.setPrescaler = 0xff;
	}

	msg.lcr = (p_priv->cflag & CSTOPB)? STOPBITS_678_2: STOPBITS_5678_1;
	switch (p_priv->cflag & CSIZE) {
	case CS5:
		msg.lcr |= USA_DATABITS_5;
		break;
	case CS6:
		msg.lcr |= USA_DATABITS_6;
		break;
	case CS7:
		msg.lcr |= USA_DATABITS_7;
		break;
	case CS8:
		msg.lcr |= USA_DATABITS_8;
		break;
	}
	if (p_priv->cflag & PARENB) {
		/* note USA_PARITY_NONE == 0 */
		msg.lcr |= (p_priv->cflag & PARODD)?
			USA_PARITY_ODD: USA_PARITY_EVEN;
	}
	msg.setLcr = 0xff;

	msg.ctsFlowControl = (p_priv->flow_control == flow_cts);
	msg.xonFlowControl = 0;
	msg.setFlowControl = 0xff;

	msg.forwardingLength = 16;
	msg.xonChar = 17;
	msg.xoffChar = 19;

	/* Opening port */
	if (reset_port == 1) {
		msg._txOn = 1;
		msg._txOff = 0;
		msg.txFlush = 0;
		msg.txBreak = 0;
		msg.rxOn = 1;
		msg.rxOff = 0;
		msg.rxFlush = 1;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0xff;
		msg.enablePort = 1;
		msg.disablePort = 0;
	}
	/* Closing port */
	else if (reset_port == 2) {
		msg._txOn = 0;
		msg._txOff = 1;
		msg.txFlush = 0;
		msg.txBreak = 0;
		msg.rxOn = 0;
		msg.rxOff = 1;
		msg.rxFlush = 1;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0;
		msg.enablePort = 0;
		msg.disablePort = 1;
	}
	/* Sending intermediate configs */
	else {
		msg._txOn = (! p_priv->break_on);
		msg._txOff = 0;
		msg.txFlush = 0;
		msg.txBreak = (p_priv->break_on);
		msg.rxOn = 0;
		msg.rxOff = 0;
		msg.rxFlush = 0;
		msg.rxForward = 0;
		msg.returnStatus = 0;
		msg.resetDataToggle = 0x0;
		msg.enablePort = 0;
		msg.disablePort = 0;
	}

		/* Do handshaking outputs */
	msg.setRts = 0xff;
	msg.rts = p_priv->rts_state;

	msg.setDtr = 0xff;
	msg.dtr = p_priv->dtr_state;

	p_priv->resend_cont = 0;
	memcpy (this_urb->transfer_buffer, &msg, sizeof(msg));

	/* send the data out the device on control endpoint */
	this_urb->transfer_buffer_length = sizeof(msg);

	this_urb->dev = serial->dev;
	if ((err = usb_submit_urb(this_urb, GFP_ATOMIC)) != 0) {
		dbg("%s - usb_submit_urb(setup) failed (%d)", __FUNCTION__, err);
	}
#if 0
	else {
		dbg("%s - usb_submit_urb(%d) OK %d bytes (end %d)", __FUNCTION__,
		    outcont_urb, this_urb->transfer_buffer_length,
		    usb_pipeendpoint(this_urb->pipe));
	}
#endif

	return (0);
}

static int keyspan_usa90_send_setup(struct usb_serial *serial,
				    struct usb_serial_port *port,
				    int reset_port)
{
	struct keyspan_usa90_portControlMessage	msg;
	struct keyspan_serial_private 		*s_priv;
	struct keyspan_port_private 		*p_priv;
	const struct keyspan_device_details	*d_details;
	struct urb				*this_urb;
	int 					err;
	u8						prescaler;

	dbg ("%s", __FUNCTION__);

	s_priv = usb_get_serial_data(serial);
	p_priv = usb_get_serial_port_data(port);
	d_details = s_priv->device_details;

	/* only do something if we have a bulk out endpoint */
	if ((this_urb = p_priv->outcont_urb) == NULL) {
		dbg("%s - oops no urb.", __FUNCTION__);
		return -1;
	}

	/* Save reset port val for resend.
	   Don't overwrite resend for open/close condition. */
	if ((reset_port + 1) > p_priv->resend_cont)
		p_priv->resend_cont = reset_port + 1;
	if (this_urb->status == -EINPROGRESS) {
		dbg ("%s already writing", __FUNCTION__);
		mdelay(5);
		return(-1);
	}

	memset(&msg, 0, sizeof (struct keyspan_usa90_portControlMessage));

	/* Only set baud rate if it's changed */
	if (p_priv->old_baud != p_priv->baud) {
		p_priv->old_baud = p_priv->baud;
		msg.setClocking = 0x01;
		if (d_details->calculate_baud_rate
		    (p_priv->baud, d_details->baudclk, &msg.baudHi,
		     &msg.baudLo, &prescaler, 0) == KEYSPAN_INVALID_BAUD_RATE ) {
			dbg("%s - Invalid baud rate %d requested, using 9600.", __FUNCTION__,
			    p_priv->baud);
			p_priv->baud = 9600;
			d_details->calculate_baud_rate (p_priv->baud, d_details->baudclk,
				&msg.baudHi, &msg.baudLo, &prescaler, 0);
		}
		msg.setRxMode = 1;
		msg.setTxMode = 1;
	}

	/* modes must always be correctly specified */
	if (p_priv->baud > 57600)
	{
		msg.rxMode = RXMODE_DMA;
		msg.txMode = TXMODE_DMA;
	}
	else
	{
		msg.rxMode = RXMODE_BYHAND;
		msg.txMode = TXMODE_BYHAND;
	}

	msg.lcr = (p_priv->cflag & CSTOPB)? STOPBITS_678_2: STOPBITS_5678_1;
	switch (p_priv->cflag & CSIZE) {
	case CS5:
		msg.lcr |= USA_DATABITS_5;
		break;
	case CS6:
		msg.lcr |= USA_DATABITS_6;
		break;
	case CS7:
		msg.lcr |= USA_DATABITS_7;
		break;
	case CS8:
		msg.lcr |= USA_DATABITS_8;
		break;
	}
	if (p_priv->cflag & PARENB) {
		/* note USA_PARITY_NONE == 0 */
		msg.lcr |= (p_priv->cflag & PARODD)?
			USA_PARITY_ODD: USA_PARITY_EVEN;
	}
	if (p_priv->old_cflag != p_priv->cflag) {
		p_priv->old_cflag = p_priv->cflag;
		msg.setLcr = 0x01;
	}

	if (p_priv->flow_control == flow_cts)
		msg.txFlowControl = TXFLOW_CTS;
	msg.setTxFlowControl = 0x01;
	msg.setRxFlowControl = 0x01;

	msg.rxForwardingLength = 16;
	msg.rxForwardingTimeout = 16;
	msg.txAckSetting = 0;
	msg.xonChar = 17;
	msg.xoffChar = 19;

	/* Opening port */
	if (reset_port == 1) {
		msg.portEnabled = 1;
		msg.rxFlush = 1;
		msg.txBreak = (p_priv->break_on);
	}
	/* Closing port */
	else if (reset_port == 2) {
		msg.portEnabled = 0;
	}
	/* Sending intermediate configs */
	else {
		if (port->open_count)
			msg.portEnabled = 1;
		msg.txBreak = (p_priv->break_on);
	}

	/* Do handshaking outputs */
	msg.setRts = 0x01;
	msg.rts = p_priv->rts_state;

	msg.setDtr = 0x01;
	msg.dtr = p_priv->dtr_state;

	p_priv->resend_cont = 0;
	memcpy (this_urb->transfer_buffer, &msg, sizeof(msg));

	/* send the data out the device on control endpoint */
	this_urb->transfer_buffer_length = sizeof(msg);

	this_urb->dev = serial->dev;
	if ((err = usb_submit_urb(this_urb, GFP_ATOMIC)) != 0) {
		dbg("%s - usb_submit_urb(setup) failed (%d)", __FUNCTION__, err);
	}
	return (0);
}

static void keyspan_send_setup(struct usb_serial_port *port, int reset_port)
{
	struct usb_serial *serial = port->serial;
	struct keyspan_serial_private *s_priv;
	const struct keyspan_device_details *d_details;

	dbg ("%s", __FUNCTION__);

	s_priv = usb_get_serial_data(serial);
	d_details = s_priv->device_details;

	switch (d_details->msg_format) {
	case msg_usa26:
		keyspan_usa26_send_setup(serial, port, reset_port);
		break;
	case msg_usa28:
		keyspan_usa28_send_setup(serial, port, reset_port);
		break;
	case msg_usa49:
		keyspan_usa49_send_setup(serial, port, reset_port);
		break;
	case msg_usa90:
		keyspan_usa90_send_setup(serial, port, reset_port);
		break;
	}
}

/* Gets called by the "real" driver (ie once firmware is loaded
   and renumeration has taken place. */
static int keyspan_startup (struct usb_serial *serial)
{
	int				i, err;
	struct usb_serial_port		*port;
	struct keyspan_serial_private 	*s_priv;
	struct keyspan_port_private	*p_priv;
	const struct keyspan_device_details	*d_details;

	dbg("%s", __FUNCTION__);

	for (i = 0; (d_details = keyspan_devices[i]) != NULL; ++i)
		if (d_details->product_id == le16_to_cpu(serial->dev->descriptor.idProduct))
			break;
	if (d_details == NULL) {
		dev_err(&serial->dev->dev, "%s - unknown product id %x\n", __FUNCTION__, le16_to_cpu(serial->dev->descriptor.idProduct));
		return 1;
	}

	/* Setup private data for serial driver */
	s_priv = kmalloc(sizeof(struct keyspan_serial_private), GFP_KERNEL);
	if (!s_priv) {
		dbg("%s - kmalloc for keyspan_serial_private failed.", __FUNCTION__);
		return -ENOMEM;
	}
	memset(s_priv, 0, sizeof(struct keyspan_serial_private));

	s_priv->device_details = d_details;
	usb_set_serial_data(serial, s_priv);

	/* Now setup per port private data */
	for (i = 0; i < serial->num_ports; i++) {
		port = serial->port[i];
		p_priv = kmalloc(sizeof(struct keyspan_port_private), GFP_KERNEL);
		if (!p_priv) {
			dbg("%s - kmalloc for keyspan_port_private (%d) failed!.", __FUNCTION__, i);
			return (1);
		}
		memset(p_priv, 0, sizeof(struct keyspan_port_private));
		p_priv->device_details = d_details;
		usb_set_serial_port_data(port, p_priv);
	}

	keyspan_setup_urbs(serial);

	s_priv->instat_urb->dev = serial->dev;
	if ((err = usb_submit_urb(s_priv->instat_urb, GFP_KERNEL)) != 0) {
		dbg("%s - submit instat urb failed %d", __FUNCTION__, err);
	}

	return (0);
}

static void keyspan_shutdown (struct usb_serial *serial)
{
	int				i, j;
	struct usb_serial_port		*port;
	struct keyspan_serial_private 	*s_priv;
	struct keyspan_port_private	*p_priv;

	dbg("%s", __FUNCTION__);

	s_priv = usb_get_serial_data(serial);

	/* Stop reading/writing urbs */
	stop_urb(s_priv->instat_urb);
	stop_urb(s_priv->glocont_urb);
	for (i = 0; i < serial->num_ports; ++i) {
		port = serial->port[i];
		p_priv = usb_get_serial_port_data(port);
		stop_urb(p_priv->inack_urb);
		stop_urb(p_priv->outcont_urb);
		for (j = 0; j < 2; j++) {
			stop_urb(p_priv->in_urbs[j]);
			stop_urb(p_priv->out_urbs[j]);
		}
	}

	/* Now free them */
	if (s_priv->instat_urb)
		usb_free_urb(s_priv->instat_urb);
	if (s_priv->glocont_urb)
		usb_free_urb(s_priv->glocont_urb);
	for (i = 0; i < serial->num_ports; ++i) {
		port = serial->port[i];
		p_priv = usb_get_serial_port_data(port);
		if (p_priv->inack_urb)
			usb_free_urb(p_priv->inack_urb);
		if (p_priv->outcont_urb)
			usb_free_urb(p_priv->outcont_urb);
		for (j = 0; j < 2; j++) {
			if (p_priv->in_urbs[j])
				usb_free_urb(p_priv->in_urbs[j]);
			if (p_priv->out_urbs[j])
				usb_free_urb(p_priv->out_urbs[j]);
		}
	}

	/*  dbg("Freeing serial->private."); */
	kfree(s_priv);

	/*  dbg("Freeing port->private."); */
	/* Now free per port private data */
	for (i = 0; i < serial->num_ports; i++) {
		port = serial->port[i];
		kfree(usb_get_serial_port_data(port));
	}
}

MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");

redpajama_set_name: RedPajamaGithub